defmodule Excon do @moduledoc """ Pure Elixir identicon creator """ @palettes { [{0, 153, 153}, {64, 179, 179}, {127, 204, 204}, {191, 229, 229}], [{0, 152, 102}, {64, 178, 140}, {127, 203, 178}, {191, 229, 217}], [{101, 44, 143}, {140, 97, 171}, {178, 149, 199}, {216, 202, 227}], [{255, 204, 51}, {255, 217, 102}, {255, 229, 153}, {255, 242, 204}], [{153, 153, 0}, {179, 179, 64}, {204, 204, 127}, {229, 229, 191}], [{102, 152, 0}, {140, 178, 64}, {178, 203, 127}, {217, 229, 191}], [{143, 44, 101}, {171, 97, 140}, {199, 149, 178}, {227, 202, 216}], [{51, 204, 255}, {102, 217, 255}, {153, 229, 255}, {204, 242, 255}], [{102, 0, 152}, {140, 64, 178}, {178, 127, 203}, {217, 191, 229}], [{255, 51, 204}, {255, 102, 217}, {255, 153, 229}, {255, 204, 242}], [{44, 101, 143}, {97, 140, 171}, {149, 178, 199}, {202, 216, 227}], [{153, 0, 153}, {179, 64, 179}, {204, 127, 204}, {229, 191, 229}], [{255, 128, 33}, {255, 169, 20}, {245, 197, 161}, {244, 210, 184}], [{250, 200, 250}, {220, 80, 220}, {230, 150, 230}, {240, 100, 240}], [{240, 192, 216}, {230, 168, 212}, {250, 217, 217}, {255, 210, 210}], [{112, 220, 113}, {183, 240, 183}, {152, 218, 150}, {218, 255, 222}] } defp mirror(thing, dir), do: do_mirror(thing, dir, []) defp do_mirror([], _, acc), do: acc |> Enum.reverse() defp do_mirror([r | rows], :ltr, acc) do do_mirror(rows, :ltr, [r |> Enum.concat(r |> Enum.reverse()) | acc]) end defp do_mirror([r | rows], :rtl, acc) do do_mirror(rows, :rtl, [r |> Enum.reverse() |> Enum.concat(r) | acc]) end defp do_mirror(rows, :ttb, _), do: rows |> Enum.concat(Enum.reverse(rows)) defp do_mirror(rows, :btt, _), do: rows |> Enum.reverse() |> Enum.concat(rows) defp hashtopat(str), do: do_hashtopat(str, []) defp do_hashtopat(<<>>, acc), do: acc |> Enum.reverse() |> Enum.chunk_every(4) defp do_hashtopat(<>, acc), do: do_hashtopat(rest, [t | acc]) defp magnify(thing, how_much) do thing |> expand_cols(how_much, []) |> expand_rows(how_much, []) end defp expand_cols([], _n, acc), do: acc |> Enum.reverse() defp expand_cols([r | rest], n, acc), do: expand_cols(rest, n, [expand_col(r, n, []) | acc]) defp expand_col([], _n, acc), do: acc |> List.flatten() |> Enum.reverse() defp expand_col([c | rest], n, acc), do: expand_col(rest, n, [List.duplicate(c, n) | acc]) defp expand_rows([], _n, acc), do: acc defp expand_rows([r | rest], n, acc), do: expand_rows(rest, n, Enum.concat(acc, expand_row(r, n, []))) defp expand_row(_i, 0, acc), do: acc defp expand_row(i, n, acc), do: expand_row(i, n - 1, [i | acc]) defp to_png(pattern, mag, pdx) do {:ok, pid} = Agent.start(fn -> [] end) %{ size: {8 * mag, 8 * mag}, mode: {:indexed, 8}, call: fn i -> Agent.update(pid, fn state -> [i | state] end) end, palette: computed_pal(pdx) } |> :png.create() |> png_append_pattern(pattern |> magnify(mag)) |> :png.close() img_data = Agent.get(pid, fn state -> state end) Agent.stop(pid) img_data |> List.flatten() |> Enum.reverse() |> Enum.join() end defp computed_pal(<>) do {:rgb, 8, @palettes |> elem(pi)} end defp png_append_pattern(png, []), do: png defp png_append_pattern(png, [r | rest]) do png |> :png.append({:row, :binary.list_to_bin(r)}) |> png_append_pattern(rest) end defp parse_options(options) do {Keyword.get(options, :filename, nil), Keyword.get(options, :magnification, 4), Keyword.get(options, :type, :png)} end @doc """ Create an indenticon from an identifying string. Options - `type`: :png or :svg (default: :png) - `filename`: a string for the file name (default: nil, image data is returned) - `magnification`: how many times to magnify the 8x8 pattern (default: 4) """ def ident(id, opts \\ []) do {fname, mag, type} = parse_options(opts) hash = Blake2.hash2b(id, 5) img = case type do :png -> ident_png(hash, mag) :svg -> ident_svg(hash, mag) _ -> {:error, "Unknown file type"} end output(img, fname, type) end defp output(img, nil, _t), do: img defp output(img, filename, type) do :ok = File.write(filename <> "." <> Atom.to_string(type), img) end defp ident_png(hash, mag) do <> = hash forpat |> hashtopat |> mirror(:ltr) |> mirror(:ttb) |> to_png(mag, forpal) end defp ident_svg(hash, mag) do << c1::bitstring-size(9), c2::bitstring-size(9), c3::bitstring-size(9), c4::bitstring-size(9), gap::integer-size(2), bgc::integer-size(2) >> = hash [s, m, e] = Enum.map([0, gap + 1, 8], fn n -> n * mag end) m1 = s + m m2 = e - m """ #{svg_bg(bgc, s, e)} """ end defp svg_bg(c, s, e) do g = 127 + c * 32 """ """ end defp svg_fill(<>) do octets = @palettes |> elem(ci) |> Enum.fetch!(w) |> Tuple.to_list() |> Enum.join(",") "fill=\"rgba(#{octets},#{0.5 + (o + 1) / 16}\"" end end