Tutorial: Identicon generator in Go
Introduction
In this tutorial we are going to build a simple identicon generator in Go. The source code can be found here: Github repo. But what exactly is an Identicon? I think most of you are familiar with the standard logo you get when creating a new account on Github.
Example:
What you see here is a visual representation of a hash value. What this basically means is that a input is hashed with a hashing algorithm(md5, sha256) and the output is used to generate a image. This way an identicon will always be the same when we generate it with the same input.
Too much talking already, lets start coding!
First we start by defining an Identicon struct
type Identicon struct {
name string
hash [16]byte
color [3]byte
}
The struct holds 3 properties:
name
: This will be the input name given to generate an identicon.hash
: 16 byte array containing the hashed value of the name, we are using[16]byte
here because our hashing functions returns a byte array with 16 values.color
: 3 byte array containing Red,Green,Blue
Nice we have defined the basic container to save the values, now we can listen to user input and hash the incoming value.
We use the Sum method from the "crypto/md5"
package. This will return us a [16]byte
checksum of the input.
func hashInput(input []byte) Identicon {
// generate checksum from input
checkSum := md5.Sum(input)
// return the identicon
return Identicon{
name: string(input),
hash: checkSum,
}
}
We can use the method as follow:
// converting string to byte array
data := []byte("bart")
// calling hashinput method on this byte array
hashInput(data)
// outputs: [245 65 70 163 252 130 171 23 229 38 86 149 178 63 100 107]
Now we need to define a color for our Identicon, we want the identicon to always be the same when we generate it again with the same input. Our trick is to take the first 3 bytes of the checksum, this way we get the same color over and over again. The first value will be Red, the second value Green and the third value will be Blue. This way we generate a valid RGB value and we can use it together with the color package in go.
func pickColor(identicon Identicon) Identicon {
// first we make a byte array with length 3
rgb := [3]byte{}
// next we copy the first 3 values from the hash to the rgb array
copy(rgb[:], identicon.hash[:3])
// we than assign it to the color value
identicon.color = rgb
// return the modified identicon
return identicon
}
Right now we have an Identicon which holds a name, color and the hash. What's special about Identicons is that the left hand side is the mirror of the right hand side.
We have to generate a 5x5 grid. If we take this byte array as example: [245 65 70 163 252 130 171 23 229 38 86 149 178 63 100 107]
. The result we want from the buildgrid function is
[ 246, 65, 70, 65, 245,
163, 252, 130, 252, 163,
171, 23, 229, 23, 171,
38, 86, 149, 86, 38,
178, 63, 100, 63, 178 ]
As you can see, the left hand side is mirrored on the right hand side in the array. We could use a matrix but for this example a simple array is sufficient. First we have to add another field to the identicon struct which will hold the value of the grid
type Identicon struct {
name string
hash [16]byte
color [3]byte
grid []byte // New property to hold the grid
}
func buildGrid(identicon Identicon) Identicon {
// Create empty grid
grid := []byte{}
// Loop over the hash from the identicon
// Increment with 3 (Chunk the array in 3 parts)
// this ensures we wont get array out of bounds error and will retrieve exactly 5 chunks of 3
for i := 0; i < len(identicon.hash) && i+3 <= len(identicon.hash)-1; i += 3 {
// Create a placeholder for the chunk
chunk := make([]byte, 5)
// Copy the items from the old array to the new array
copy(chunk, identicon.hash[i:i+3])
chunk[3] = chunk[1] // mirror the second value in the chunk
chunk[4] = chunk[0] // mirror the first value in the chunk
grid = append(grid, chunk...) // append the chunk to the grid
}
identicon.grid = grid // set the grid property on the identicon
return identicon // finally return the modified identicon
}
Now we almost have a grid we can use to draw a real image, but first we have to specify which points in the grid we are going to fill with the color. If you look at the grid you can see it would be a nice fit if we only fill in the odd values. For this to work we have to keep track of the index inside the grid, because otherwise we won't know which of the items to fill. We do this by specifying another struct, which will hold the value and the index of the item in the grid.
type GridPoint struct {
value byte
index int
}
We have to keep track of the GridPoints inside the Identicon structure.
type Identicon struct {
name string
hash [16]byte
color [3]byte
grid []byte
gridPoints []GridPoint // Filtered points in the grid
}
The GridPoints will be the values we are going to fill in with the previous generated color. Lets have a look at the method to filter the odds grid.
func filterOddSquares(identicon Identicon) Identicon {
grid := []GridPoint{} // create a placeholder to hold the values of the loop
for i, code := range identicon.grid { // loop over the grid
if code%2 == 0 { // check if the value is odd or not
// create a new Gridpoint where we save the value and the index in the grid
point := GridPoint{
value: code,
index: i,
}
// append the item to the new grid
grid = append(grid, point)
}
}
// set the property
identicon.gridPoints = grid
return identicon // return the modified identicon
}
So right now we have an Identicon which has the values we need to fill in, but we can't draw an image of only those values. We have to transform them to a pixelmap. Because we want to draw a rectangle for every odd point we need to calculate the topLeft x and y and the bottom right x and y. With those values we can make a rectangle.
First we specify a structure to hold the dimensions.
type Point struct {
x, y int
}
type DrawingPoint struct {
topLeft Point
bottomRight Point
}
But how can we relate the values from the grid to actual pixels on a pixelmap? We specify here first that our image will be of size 250 pixels by 250 pixels. As mentioned before our grid was 5x5 so 1 piece in the grid will be 50 pixels by 50 pixels, because 250/5 = 50. With this knowledge we can calculate the borders to draw. With two simple formula's:
- horizontal: (x % 5) * 50
- vertical: (x / 50) 50 where x = index of the gridpoint*
I won't zoom in any further on the formula's, because they are out of scope of this article. But remember for now that they allow us to calculate the positions in the pixelmap. Lets have a look at how we can use those formula's to generate dimensions for our rectangle. First we modify our Identicon struct to also hold a property of pixelMap. This pixelMap will contain the dimensions to draw in the png.
type Identicon struct {
name string
hash [16]byte
color [3]byte
grid []byte
gridPoints []GridPoint
pixelMap []DrawingPoint // pixelMap for drawing
}
Now we can save the pixelmap in our Identicon struct
func buildPixelMap(identicon Identicon) Identicon {
drawingPoints := []DrawingPoint{} // define placeholder for drawingpoints
// Closure, this function returns a Drawingpoint
pixelFunc := func(p GridPoint) DrawingPoint {
// This is the formula, we use the index from the gridpoint to calculate the horizontal dimension
horizontal := (p.index % 5) * 50
// This is the formula, we use the index from the gridpoint to calculate the vertical dimension
vertical := (p.index / 5) * 50
// this is the topleft point with x and the y
topLeft := Point{horizontal, vertical}
// the bottom right point is just the topleft point +50 because 1 block in the grid is 50x50
bottomRight := Point{horizontal + 50, vertical + 50}
return DrawingPoint{ // We then return the drawingpoint
topLeft,
bottomRight,
}
}
for _, gridPoint := range identicon.gridPoints {
// for every gridPoint we calculate the drawingpoints and we add them to the array
drawingPoints = append(drawingPoints, pixelFunc(gridPoint))
}
identicon.pixelMap = drawingPoints // set the drawingpoint value on the identicon
return identicon // return the modified identicon
}
Sweet our datastructure now also holds a pixelmap we can use to draw rectangles. To actually draw rectangle we need to take the four dimensions from the DrawingPoint struct
. And pass them to a draw function.
for drawing rectangles I'll use a external library draw2dRepo. This library make's it very easy for us to draw a rectangle.
We pass in a image, color and the dimensions to our rect function. This will create a filled rectangle for us.
func rect(img *image.RGBA, col color.Color, x1, y1, x2, y2 float64) {
gc := draw2dimg.NewGraphicContext(img) // Prepare new image context
gc.SetFillColor(col) // set the color
gc.MoveTo(x1, y1) // move to the topleft in the image
// Draw the lines for the dimensions
gc.LineTo(x1, y1)
gc.LineTo(x1, y2)
gc.MoveTo(x2, y1) // move to the right in the image
// Draw the lines for the dimensions
gc.LineTo(x2, y1)
gc.LineTo(x2, y2)
// Set the linewidth to zero
gc.SetLineWidth(0)
// Fill the stroke so the rectangle will be filled
gc.FillStroke()
}
Now we can draw filled rectangles. We just have to loop over the pixelMap and call the rectangle function.
func drawRectangle(identicon Identicon) error {
// We create our default image containing a 250x250 rectangle
var img = image.NewRGBA(image.Rect(0, 0, 250, 250))
// We retrieve the color from the color property on the identicon
col := color.RGBA{identicon.color[0], identicon.color[1], identicon.color[2], 255}
// Loop over the pixelmap and call the rect function with the img, color and the dimensions
for _, pixel := range identicon.pixelMap {
rect(
img,
col,
float64(pixel.topLeft.x),
float64(pixel.topLeft.y),
float64(pixel.bottomRight.x),
float64(pixel.bottomRight.y)
)
}
// Finally save the image to disk
return draw2dimg.SaveToPngFile(identicon.name+".png", img)
}
Sweet!! we have made an Identicon generator, all the functions are there but there is only thing left. As you may have noticed most of the functions where modifying an Identicon and then returning an Identicon. This is a perfect example for a pipe function, we can make a placeholder for the function like this:
type Apply func(Identicon) Identicon
And we can define a function which takes x amount of Apply types and apply them to an Identicon
func pipe(identicon Identicon, funcs ...Apply) Identicon {
for _, applyer := range funcs {
identicon = applyer(identicon)
}
return identicon
}
Finally lets wrap everything in a main funtion and define a flag for the name input.
func main() {
var (
name = flag.String("name", "", "Set the name where you want to generate an Identicon for")
)
flag.Parse()
if *name == "" {
flag.Usage()
os.Exit(0)
}
data := []byte(*name)
identicon := hashInput(data)
// Pass in the identicon, call the methods which you want to transform
identicon = pipe(identicon, pickColor, buildGrid, filterOddSquares, buildPixelMap)
// we can use the identicon to insert to our drawRectangle function
if err := drawRectangle(identicon); err != nil {
log.Fatalln(err)
}
}
Running this with input bart will give us:

Congratulations, you just made your own identicon generator.
Conclusion
Thanks for reading my blog post, I hope you all enjoyed it. I was inpsired by a elixir course on udemy where this was a exercise. I thought it would be fun to rewrite it in go. In a few lines you can create your own identicon generator, currently you can't generate bigger or smaller identicons. I leave that one to implement for the reader.
Happy programming!