Building Identicons and Playing with Clojure
I’ve been playing with Clojure on and off for a little while now. I’ve worked through problems on 4 Clojure and Exercism.io but I’ve been looking for a small project to take on. Last Wednesday when GitHub announced identicons for users without avatars and I found my project. My goal was to take a string and output an identicon for it. For identicons a particular input should always generate the same identicon. Also, the identicons should be unique for each input.
How do I make an image?
First I needed to figure out how to generate an image in Clojure1. After a brief search it looked like my best bet was to outsource this task to Java.
Start by creating a BufferedImage
:
icon (BufferedImage. 120 120 BufferedImage/TYPE_INT_RGB)
Then get a Graphics2D
so you can draw on the image:
draw (.createGraphics icon)
From there you can draw lines, fill in rectangles, and color like a blissful two-year-old.
;; draw a white 8x8 square drawn in the upper left corner
(.setColor draw (Color/WHITE))
(.fillRect draw 0 0 8 8)))
When you’re done, saving is easy:
(ImageIO/write icon "png" (File. "some-file-name.png"))
How do I generate a more consistent identifier?
Knowing that I could make images I turned my attention to an identifier. I was expecting to receive a string but that could contain anything and be of any length. In order to simplify things I needed a consistent format to work with. Hashing the input was the easiest solution. Even though it has collision issues I decided to use MD5. It’s fast and close enough to unique for the purposes of my project. MD5 generates a 32 digit string of hexidecimal numbers regardless of what input you give it. Clojure was ready for me this time with a library called Digest.
> (digest/md5 "Aaron")
;; "1c0a11cc4ddc0dbd3fa4d77232a4e22e"
How do I translate an MD5 string into an image?
A Sequence of Booleans
With the basics out of the way it was time to bring it all together. I needed a way to turn the MD5 into a unique image. My identicon, like GitHub’s, is a grid of blocks. I could turn the MD5 into a sequence of on/off flags. Then I could leverage that to turn on certain blocks. Since each characters represents a hexidecimal digit converting them to booleans was easy. All I had to do was check to see if the digit was between 0-7 or 8-15.
;; to-numbers converts the provided MD5 into a sequence of integers
(map #(> % 7) (to-numbers md5))
Mapping to the Grid
GitHub made their identicons symmetrical. I think it creates a more appealing visual so I decided to do the same. This meant that I was only generating half of the grid and then mirroring it. It also means that on a 6x6 grid I’m only using 18 of my 32 hexidecimal digits. Now I’ve really increased the chance of a collision but this isn’t production so I’m fine with it.
(def tiles-per-side 6)
(defn- in-row
"Return the row for a particular position in the seq."
[pos]
(quot pos (/ tiles-per-side 2)))
(defn- in-col
"Return the column for a particular position in the seq."
[pos]
(rem pos (/ tiles-per-side 2)))
(defn- draw-tile
"Fill in a tile at a particular position starting from the left of the image."
[draw tile-size pos]
(.fillRect draw
(* (in-col pos) tile-size)
(* (in-row pos) tile-size)
tile-size tile-size))
The code above only draws half of the grid.
There’s another function called draw-mirror-tile
that takes the same inputs and draws the mirrored version of the tile.
I imagine there is a way to draw one half and mirror it using the underlying Java library but drawing twice was easier in the moment.
Generating the Identicon
I made a function called generate
that accepts an identifier and a size.
The size needs to be a multiple of 6 so that it fits the grid properly.
(identicon.core/generate "Aaron" 120)
How do I add color?
The identicon would look better with colors.
Back in the Java docs I found that I could add color with setColor
.
I couldn’t figure out the proper way to set a background color so I started by drawing a white rectangle over the entire image.
;; size passed as the second argument to generate
(.setColor draw (Color/WHITE))
(.fillRect draw 0 0 size size)))
Then I found a 16 color pastel palette and used the first digit of the hexidecimal sequence to pick one.
(defn- get-color
[pos]
(nth '(
(246 150 121) ;; Pastel Red
(249 173 129) ;; Pastel Red Orange
...
(244 154 193) ;; Pastel Magenta
(245 152 157) ;; Pastel Magenta Red
) pos))'))
;; in the generate function
(let
[r g b] (get-color (first (to-numbers md5)))
color (Color. r g b)
(do
(.setColor draw color)
;; other stuff))
There’s probably a better way to destructure the sequence that get-color
returns and pass it directly to the Color
constructor but I got sick of trying to figure it out.
Now when we run the same command from earlier we get a colorized version.
If you want to see all of the code together you can find it here.
Takeaway
Building identicons turned out to be a good project. It gave me a chance to dive into Clojure in a way that I hadn’t done before. However, I did hit some bumps along the way.
Reaching for Java felt wrong.
I can’t help but wonder how often the community looks to Java to solve their problems.
It’s nice having fast, reliable, well built libraries for free, but Java is a different paradigm and it feels like it bleeds through.
In my generate
function I call a number of methods that modify draw
and don’t return anything of use.
It feels like the wrong approach in a language that lauds immutability.
It might be that I’m not experienced enough and there are ways around these issues that I will find as I learn.
I certainly hope so because I plan to continue exploring.
-
Version 1.5.1 ↩