Creating Identicons from Usernames with Elixir: A Visual Representation of Identity
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:
- 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.
- Functional Programming Paradigm: Elixir is a functional programming language, which encourages immutable data structures and pure functions. This make clear, concise, and maintainable code.
- 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
- Any platform based Code Editor, I am using VS Code For this Project
- Basic knowledge of Elixir
Steps
The steps required to build this project are given bellow the following.
- 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:
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.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.grid
: Thegrid
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.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:
- Chunking Hexadecimal List: The
hex
list is chunked into groups of three elements each usingEnum.chunk_every/4
. Each chunk represents a row in the grid. - 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. - 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
. - 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:
- Iterating Over Grid: The function iterates over each tuple in the
grid
configuration obtained from theIconModel
struct. - 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.
- 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. - Updating IconModel: Finally, the
pixel_map
field of theIconModel
struct is updated with thegrid_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
- 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. - Setting Fill Color: The
fill
variable is assigned the color specified in theIconModel
, which represents the RGB color values. This color will be used to fill the shapes drawn on the canvas. - Drawing Shapes: Using
Enum.each/2
, the function iterates over each pixel coordinate tuple in thepixel_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. - Rendering Image: After all shapes have been drawn on the canvas, the
:egd.render/1
function is called to render the final image. - 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:
- Output Message: A message is printed to indicate that the image has been saved with the specified filename.
- 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")
Thanks for reading! My name is Abhishek, I have an passion of building app and learning new technology.