lib/identicon.ex

defmodule Identicon do
  @moduledoc """
  `Identicon` is a module that that helps to convert a sting
  into a spacial type of image
  """

  @doc """
  Main function that will do the conversion.
  """
  @spec main(String.t()) :: any
  def main(str) do
    str
    |> string_to_md5
    |> pick_color
    |> build_grid
    |> build_pixel_map
    |> draw_image
    |> save_image(str)
  end

  @spec save_image(binary, String.t()) :: :ok | {:error, atom}
  def save_image(image, filename) do
    File.write("#{filename}.png", image)
  end

  @spec draw_image(%Identicon.Image{}) :: binary
  def draw_image(%Identicon.Image{color: color, pixel_map: pixel_map}) do
    image = :egd.create(250, 250)
    fill = :egd.color(color)
    brown = :egd.color({97, 88, 109})
    yellow = :egd.color({226, 211, 182})

    :egd.filledRectangle(image, {0, 0}, {250, 250}, brown)

    for i <- 0..5 do
      for j <- 0..5 do
        h1 = i * 50
        h2 = j * 50
        :egd.filledRectangle(image, {h1 + 2, h2 + 2}, {h1 + 48, h2 + 48}, yellow)
      end
    end

    Enum.each(
      pixel_map,
      fn {start, stop} ->
        :egd.filledRectangle(image, start, stop, fill)
      end
    )

    :egd.render(image)
  end

  @spec build_pixel_map(%Identicon.Image{}) :: %Identicon.Image{}
  def build_pixel_map(%Identicon.Image{grid: grid} = image) do
    pixel_map =
      Enum.map(grid, fn {_value, index} ->
        horizontal = rem(index, 5) * 50
        vertical = div(index, 5) * 50

        top_left = {horizontal + 2, vertical + 2}
        bottom_right = {horizontal + 48, vertical + 48}

        {top_left, bottom_right}
      end)

    %Identicon.Image{image | pixel_map: pixel_map}
  end

  @spec build_grid(%Identicon.Image{}) :: %Identicon.Image{}
  def build_grid(%Identicon.Image{hex: hex} = image) do
    grid =
      hex
      |> Enum.chunk_every(3, 3, :discard)
      |> Enum.map(&mirror_row/1)
      |> List.flatten()
      |> Enum.with_index()
      |> filter_odds_squares

    %Identicon.Image{image | grid: grid}
  end

  @spec filter_odds_squares([{term, integer}]) :: [{term, integer}]
  def filter_odds_squares(grid) do
    Enum.filter(grid, fn {value, _index} ->
      rem(value, 2) == 0
    end)
  end

  @spec mirror_row([byte]) :: [byte]
  def mirror_row(row) do
    [hex_1, hex_2 | _tail] = row
    row ++ [hex_2, hex_1]
  end

  @spec pick_color(%Identicon.Image{}) :: %Identicon.Image{}
  def pick_color(%Identicon.Image{hex: [r, g, b | _tail]} = image) do
    %Identicon.Image{image | color: {r, g, b}}
  end

  @spec string_to_md5(Stream.t()) :: %Identicon.Image{hex: [byte]}
  def string_to_md5(str) do
    hex =
      :crypto.hash(:md5, str)
      |> :binary.bin_to_list()

    %Identicon.Image{hex: hex}
  end
end