Creating Identicons from Usernames with Elixir: A Visual Representation of Identity

Abhishek Biswas
8 min readApr 28, 2024

In today’s interconnected digital world, establishing and maintaining online identities is a fundamental aspect of our lives. From social media platforms to online forums and gaming communities, usernames serve as unique identifiers that help us navigate and participate in various online spaces. However, these usernames often lack visual representation, making it challenging to quickly identify and differentiate users in a visually appealing manner.

Identicons are the graphical representations generated from usernames that provide a visual fingerprint of sorts, allowing users to recognize each other more easily. In this article, we’ll explore how to create Identicons using Elixir

Why Elixir?

Elixir language offers a compelling combination of features that make it an excellent choice for creating Identicons:

  1. Concurrency and Scalability: Elixir is built on the Erlang virtual machine (BEAM), which is renowned for its lightweight concurrency model and fault-tolerant design. This makes Elixir well-suited for handling concurrent tasks efficiently, allowing us to generate Identicons quickly and reliably even under heavy loads.
  2. Functional Programming Paradigm: Elixir is a functional programming language, which encourages immutable data structures and pure functions. This make clear, concise, and maintainable code.
  3. Pattern Matching and Metaprogramming: Elixir’s powerful pattern matching capabilities enable elegant and expressive code, making it easier to manipulate data structures and generate Identicon patterns dynamically.

Requirements

  1. Any platform based Code Editor, I am using VS Code For this Project
  2. Basic knowledge of Elixir

Steps

The steps required to build this project are given bellow the following.

  1. Project Setup with Mix

The process begins with setting up the project using Mix, Elixir’s build tool. With Mix, we create a new project directory and configure the necessary dependencies and project structure.

$ mix new icon_gen
$ cd icon_gen

2. Structuring Identicon Model

To manage Identicon data effectively, we define a struct model that represents Identicons. This struct organizes essential attributes and facilitates seamless data manipulation throughout the process.

defmodule IconModel do
defstruct hex: nil, color: nil, grid: nil, pixel_map: nil
end

The IconModel module defines a struct for representing an Identicon. This struct provides a structured way to organize and manage the various components of an Identicon, facilitating its creation and manipulation within the application.

The struct, defined using the defstruct macro, contains four fields:

  1. hex: This field holds the hexadecimal list generated from the hash of the input name. It represents the raw hash data used as the basis for generating the Identicon.
  2. color: This field contains the RGB color values used to color the Identicon. It is represented as a map with keys for red (r), green (g), and blue (b) values.
  3. grid: The grid field represents the even image pixel values of the Identicon. It holds the grid configuration that determines which pixels should be colored to create the Identicon's pattern.
  4. pixel_map: This field contains the mapping of grid coordinates to the color to be applied. It defines which grid cells should be colored and specifies the color to use for each cell.

By organizing the Identicon data into this structured format, the IconModel struct promotes clarity, modularity, and ease of manipulation within the application.

3. Generating MD5 Hash

Upon project setup, we implement functionality to generate an MD5 hash list from the username provided by the user. This hash serves as the basis for creating unique Identicons.

def createHashStringFromName(inputName) do
hashList =
:crypto.hash(:md5, inputName)
|> :binary.bin_to_list()

%IconModel{hex: hashList}
end

In the provided Elixir function createHashStringFromName, we begin by generating an MD5 hash from the input name using the :crypto.hash/2 function provided by Erlang. This function computes the MD5 hash of the input string (inputName). The resulting hash is then converted into a binary format using the :binary.bin_to_list/1 function, transforming it into a list of integers.

After obtaining the hash list, we encapsulate it within an %IconModel{} struct. This struct, represented by %IconModel{hex: hashList}, serves as a data model for organizing and managing the hash list. It provides a structured way to handle the hash data, facilitating its manipulation and utilization within the Identicon generation process.

By structuring the hash list within an %IconModel{} struct, we enhance the clarity and organization of the codebase, making it easier to work with and maintain. Additionally, this approach enables seamless integration with other parts of the Identicon generation system, promoting modularity and extensibility.

4. Colour Selection from Hash

Next, we extract the first three values from the hash list to determine the color scheme of the Identicon’s grid. These values dictate the primary colors used in the Identicon’s design.

def extractColor(%IconModel{hex: [r, g, b | _tail]} = imgByteList) do
%IconModel{imgByteList | color: {r, g, b}}
end

5. Building Image Grid and Filtering Which Grid to Colour

We further refine the hash list by filtering out odd-indexed elements. This step helps ensure symmetry and balance in the Identicon’s pixel grid, enhancing its visual appeal.

def buildGrid(%IconModel{hex: hex} = imageStruct) do
res =
hex
|> Enum.chunk_every(3, 3, :discard)
|> Enum.map(&mirror_row/1)
|> List.flatten
|> Enum.with_index

%IconModel{imageStruct | grid: res}
end
def mirror_row([first, mid | _tail] = image_list) do
image_list ++ [mid, first]
end
def filter_odd_blocks(%IconModel{grid: grid} = image) do
grid =
Enum.filter(grid, fn {value, _idx} ->
rem(value, 2) == 0
end)

%IconModel{image | grid: grid}
end

Identicon creation process, two key functions play a significant role: buildGrid/1 and filter_odd_blocks/1.

The buildGrid/1 function is responsible for constructing the grid structure of the Identicon. It takes the hexadecimal list (hex) generated from the input name and processes it to create a grid configuration that determines the pattern of the Identicon.

This function operates in several steps:

  1. Chunking Hexadecimal List: The hex list is chunked into groups of three elements each using Enum.chunk_every/4. Each chunk represents a row in the grid.
  2. Mirroring Rows: The mirror_row/1 function is applied to each chunk to mirror the first and middle elements, effectively doubling their occurrence within the row.
  3. Flattening and Indexing: The resulting list of mirrored rows is flattened into a single list of elements. Then, each element is enumerated with its index using Enum.with_index/1.
  4. Grid Configuration: The enumerated list of tuples represents the grid configuration of the Identicon. Each tuple contains a value (representing a cell in the grid) and its corresponding index.

The filter_odd_blocks/1 function complements the buildGrid/1 function by refining the grid configuration. It removes odd-indexed blocks from the grid, ensuring a balanced and visually appealing pattern. This function operates by filtering out tuples whose index values are odd, effectively removing every other block from the grid.

6. Building Pixel Map

Using the filtered hash list, we construct a pixel map that forms the foundation of the Identicon’s graphical representation. This pixel map serves as the canvas for drawing the Identicon’s distinctive pattern.

def build_pixel_map(%IconModel{grid: grid} = image) do
grid_map_color =
Enum.map(grid, fn {_value, idx} ->
horizental_distance = rem(idx, 5) * 50
vertical_distance = div(idx, 5) * 50
top_left = {horizental_distance, vertical_distance}
bottom_right = {horizental_distance + 50, vertical_distance + 50}
{top_left, bottom_right}
end)

%IconModel{image | pixel_map: grid_map_color}
end

The build_pixel_map/1 function is a crucial step in the creation of our Identicon, as it determines the mapping between grid cells and their corresponding pixel coordinates on the image. This mapping facilitates the accurate drawing of the Identicon based on the grid configuration previously established.

Here’s a explanation of how the build_pixel_map/1 function works:

  1. Iterating Over Grid: The function iterates over each tuple in the grid configuration obtained from the IconModel struct.
  2. Calculating Pixel Coordinates: For each grid cell, the function calculates the pixel coordinates of its top-left and bottom-right corners within the image canvas. This calculation is based on the index of the grid cell and assumes a fixed cell size of 50x50 pixels.
  3. Mapping Grid to Pixel Coordinates: The calculated pixel coordinates are stored as a tuple representing the top-left and bottom-right corners of the cell’s bounding box. These tuples are then collected into a list, grid_map_color, which serves as the pixel map for the Identicon.
  4. Updating IconModel: Finally, the pixel_map field of the IconModel struct is updated with the grid_map_color list, completing the mapping process.

7. Drawing and Saving Identicon

With the pixel map in place, we proceed to draw the Identicon’s pattern according to the specified design rules. Once complete, the generated Identicon is saved locally, ready for use or distribution

defp deps do
[{:egd, github: "erlang/egd"}]
end
def draw_image(%IconModel{color: color, pixel_map: pixel_map}) do
image = :egd.create(250, 250)
fill = :egd.color(color)
Enum.each(pixel_map, fn {x, y} ->
:egd.filledRectangle(image, x, y, fill)
end)
IO.puts("Image Generated .....")
:egd.render(image)
end
def save_image(image, input) do
IO.puts("Image saved with #{input}.png name .....")
File.write("./images/#{input}.png", image)
end

The draw_image/1 function is responsible for rendering the Identicon based on the provided color information and pixel map. This function utilizes the :egd library, which is an Erlang Graphics Library, to create and draw the image.

As :egd is not present in Elixir standard library. so, we will install this library using mix. After installing

  1. Creating Image: The function initializes a new image canvas using the :egd.create/2 function, specifying the dimensions of the canvas as 250x250 pixels. This canvas will serve as the drawing surface for the Identicon.
  2. Setting Fill Color: The fill variable is assigned the color specified in the IconModel, which represents the RGB color values. This color will be used to fill the shapes drawn on the canvas.
  3. Drawing Shapes: Using Enum.each/2, the function iterates over each pixel coordinate tuple in the pixel_map. For each tuple (x, y), representing the top-left corner of a cell, a filled rectangle is drawn on the canvas at that position using :egd.filledRectangle/5. The rectangle is filled with the specified color.
  4. Rendering Image: After all shapes have been drawn on the canvas, the :egd.render/1 function is called to render the final image.
  5. Output Message: Finally, a message is printed to indicate that the image has been generated.

The save_image/2 function complements draw_image/1 by saving the generated image to the file system. It takes the generated image data and a filename (input) as arguments.

Here’s how the save_image/2 function works:

  1. Output Message: A message is printed to indicate that the image has been saved with the specified filename.
  2. Writing Image to File: The File.write/2 function is used to write the image data to a file in the ./images directory with the specified filename and .png extension.

8. Combining all part

Creating an main function which we will invoke by passing username and make an identicons by going through processing operation which is hidden to user.

def main(input) do
input
|> createHashStringFromName
|> extractColor
|> buildGrid
|> filter_odd_blocks
|> build_pixel_map
|> draw_image
|> save_image(input)
end

Complete Code Logic

This is complete code Logic for building Identicons from username using elixir

#icon_gen.ex 

defmodule IconGen do
def main(input) do
input
|> createHashStringFromName
|> extractColor
|> buildGrid
|> filter_odd_blocks
|> build_pixel_map
|> draw_image
|> save_image(input)
end

def createHashStringFromName(inputName) do
hashList =
:crypto.hash(:md5, inputName)
|> :binary.bin_to_list()

%IconModel{hex: hashList}
end

def draw_image(%IconModel{color: color, pixel_map: pixel_map}) do
image = :egd.create(250, 250)
fill = :egd.color(color)
Enum.each(pixel_map, fn {x, y} ->
:egd.filledRectangle(image, x, y, fill)
end)
IO.puts("Image Generated .....")
:egd.render(image)
end

def save_image(image, input) do
IO.puts("Image saved with #{input}.png name .....")
File.write("./images/#{input}.png", image)
end

def build_pixel_map(%IconModel{grid: grid} = image) do
grid_map_color =
Enum.map(grid, fn {_value, idx} ->
horizental_distance = rem(idx, 5) * 50
vertical_distance = div(idx, 5) * 50
top_left = {horizental_distance, vertical_distance}
bottom_right = {horizental_distance + 50, vertical_distance + 50}
{top_left, bottom_right}
end)

%IconModel{image | pixel_map: grid_map_color}
end


def filter_odd_blocks(%IconModel{grid: grid} = image) do
grid =
Enum.filter(grid, fn {value, _idx} ->
rem(value, 2) == 0
end)

%IconModel{image | grid: grid}
end

def extractColor(%IconModel{hex: [r, g, b | _tail]} = imgByteList) do
%IconModel{imgByteList | color: {r, g, b}}
end

def buildGrid(%IconModel{hex: hex} = imageStruct) do
res =
hex
|> Enum.chunk_every(3, 3, :discard)
|> Enum.map(&mirror_row/1)
|> List.flatten
|> Enum.with_index

%IconModel{imageStruct | grid: res}
end

@spec mirror_row([...]) :: [...]
def mirror_row([first, mid | _tail] = image_list) do
image_list ++ [mid, first]
end
end
# icon_model.ex
defmodule IconModel do
defstruct hex: nil, color: nil, grid: nil, pixel_map: nil
end

Results

This are the result which is created when use username as asdf . This image is saved in ./image directory of project root. Below the command how to run the project and how to generate Identicons.

$ iex -S mix
$ iex> IconGen.main("asdf")
Identicons for username “asdf”

Thanks for reading! My name is Abhishek, I have an passion of building app and learning new technology.

--

--