Identicons are
visual representations of text. You've probably seen them before; they're often used as avatar replacements for users that haven't provided their own photo. When GitHub announced their
identicon implementation for any users without a
Gravatar, I was struck by how personable they were. They didn't have the same appearance of geometrical abstraction, but looked like faces, pixel monsters, space invaders, or symbols. It was easy to presume meaning in them.
For whatever reason, I was moved to try and write an identicon implementation last night. Particularly after seeing that MD5 hashes are hexadecimal, so getting the color would be easy. I started with JavaScript because it's so easy to try things out by just opening a browser console, but found that there's not a native MD5 hashing function in JavaScript and quickly moved on over to PHP. GitHub was prominently my inspiration, particularly informing the 5x5 "pixel" grid.
The steps for the program are:
- Get String
- Convert string to MD5 hash
- Extract identicon color from hash
- Convert hash values to boolean values to determine each block in the 5x5 grid
- Draw image based off of values
- Output image
See it in action.
Get String
This part is easy. It's a call to get a resource, so the string should be a GET parameter. I wanted the identicon to be case insensitive so that common case insensitive personal identifiers, like email addresses, would return the same image.
// Get string from query string
$string = $_GET['string'] ? strtolower($_GET['string']) : "";
Convert String to MD5 Hash
This is also easy with PHP; it has it's own MD5 hashing function.
// Get string from query string
$string = $_GET['string'] ? strtolower($_GET['string']) : "";
// Convert string to MD5
$hash = md5($string);
Extract identicon color from hash
With an MD5 hash, this is easy. We can just take the first six characters, and there's our hex color code.
// Get string from query string
$string = $_GET['string'] ? strtolower($_GET['string']) : "";
// Convert string to MD5
$hash = md5($string);
// Get color from first 6 characters
$color = substr($hash, 0, 6);
Convert Hash Values to Booleans
I converted each hexadecimal character to decimal and then tested for odd or even to determine if a "pixel" in the identicon is turned on or off. Because I had the data available, I chose to offset my type conversion by the number of characters used to determine color.
// Get string from query string
$string = $_GET['string'] ? strtolower($_GET['string']) : "";
// Convert string to MD5
$hash = md5($string);
// Get color from first 6 characters
$color = substr($hash, 0, 6);
// Create an array to store our boolean "pixel" values
$pixels = array();
// Make it a 5x5 multidimensional array
for ($i = 0; $i < 5; $i++) {
for ($j = 0; $j < 5; $j++) {
$pixels[$i][$j] = hexdec(substr($hash, ($i * 5) + $j + 6, 1))%2 === 0;
}
}
Draw Image
I don't do much image manipulation, so this part was generally new to me.
imagecolorallocate
takes the color as RGB values, so the hex code needed to be converted.
// Get string from query string
$string = $_GET['string'] ? strtolower($_GET['string']) : "";
// Convert string to MD5
$hash = md5($string);
// Get color from first 6 characters
$color = substr($hash, 0, 6);
// Create an array to store our boolean "pixel" values
$pixels = array();
// Make it a 5x5 multidimensional array
for ($i = 0; $i < 5; $i++) {
for ($j = 0; $j < 5; $j++) {
$pixels[$i][$j] = hexdec(substr($hash, ($i * 5) + $j + 6, 1))%2 === 0;
}
}
// Create image
$image = imagecreatetruecolor(400, 400);
// Allocate the primary color. The hex code we assigned earlier needs to be decoded to RGB
$color = imagecolorallocate($image, hexdec(substr($color,0,2)), hexdec(substr($color,2,2)), hexdec(substr($color,4,2)));
// And a background color
$bg = imagecolorallocate($image, 238, 238, 238);
// Color the pixels
for ($k = 0; $k < count($pixels); $k++) {
for ($l = 0; $l < count($pixels[$k]); $l++) {
// Default pixel color is the background color
$pixelColor = $bg;
// If the value in the $pixels array is true, make the pixel color the primary color
if ($pixels[$k][$l]) {
$pixelColor = $color;
}
// Color the pixel. In a 400x400px image with a 5x5 grid of "pixels", each "pixel" is 80x80px
imagefilledrectangle($image, $k * 80, $l * 80, ($k + 1) * 80, ($l + 1) * 80, $pixelColor);
}
}
Output Image
Finally, output all that data as an image.
// Get string from query string
$string = $_GET['string'] ? strtolower($_GET['string']) : "";
// Convert string to MD5
$hash = md5($string);
// Get color from first 6 characters
$color = substr($hash, 0, 6);
// Create an array to store our boolean "pixel" values
$pixels = array();
// Make it a 5x5 multidimensional array
for ($i = 0; $i < 5; $i++) {
for ($j = 0; $j < 5; $j++) {
$pixels[$i][$j] = hexdec(substr($hash, ($i * 5) + $j + 6, 1))%2 === 0;
}
}
// Create image
$image = imagecreatetruecolor(400, 400);
// Allocate the primary color. The hex code we assigned earlier needs to be decoded to RGB
$color = imagecolorallocate($image, hexdec(substr($color,0,2)), hexdec(substr($color,2,2)), hexdec(substr($color,4,2)));
// And a background color
$bg = imagecolorallocate($image, 238, 238, 238);
// Color the pixels
for ($k = 0; $k < count($pixels); $k++) {
for ($l = 0; $l < count($pixels[$k]); $l++) {
// Default pixel color is the background color
$pixelColor = $bg;
// If the value in the $pixels array is true, make the pixel color the primary color
if ($pixels[$k][$l]) {
$pixelColor = $color;
}
// Color the pixel. In a 400x400px image with a 5x5 grid of "pixels", each "pixel" is 80x80px
imagefilledrectangle($image, $k * 80, $l * 80, ($k + 1) * 80, ($l + 1) * 80, $pixelColor);
}
}
// Output the image
header('Content-type: image/png');
imagepng($image);
Advanced Features
That's the basic implementation. My implementation has a few extras:
- Accepts a size parameter
- Adds padding around the image
- Adds an extra layer of variance by using one of the hash characters to determine the "switch" of whether odd or even creates a filled pixel
- With the basic implementation, I felt that the results looked far too random. They didn't have near the personality that GitHub's identicons have. On closer inspection, I realized that their identicons are symmetrical. The right side is a mirror image of the left. I changed my pixel coloring algorithm to mirror the columns 1 and 2 in columns 4 and 5. This dramatically improved their viability as avatar substitutes.
View demo of identicons with these extra features.
To Do
There are a few things that I think would be fun:
- Make the number of pixels in the grid configurable
- Make the background color configurable
- Port the whole thing to JavaScript, using canvas