Mandelbrot Set

Code

Mandelbrot module · lib/phx_demo/examples/mandelbrot.ex
defmodule PhxDemo.Examples.Mandelbrot do
  @mandelbrot (
                width = 200
                height = 200
                max_iter = 50
                x_min = -2.0
                x_max = 0.7
                y_min = -1.35
                y_max = 1.35

                color = fn
                  n, max when n >= max ->
                    "#000000"

                  n, max ->
                    t = n / max
                    r = round(9 * (1 - t) * t * t * t * 255)
                    g = round(15 * (1 - t) * (1 - t) * t * t * 255)
                    b = round(8.5 * (1 - t) * (1 - t) * (1 - t) * t * 255)
                    "rgb(#{min(r, 255)}, #{min(g, 255)}, #{min(b, 255)})"
                end

                iterate = fn
                  _iter, _zr, _zi, _cr, _ci, n, max when n >= max ->
                    max

                  iter, zr, zi, cr, ci, n, max ->
                    zr2 = zr * zr
                    zi2 = zi * zi

                    if zr2 + zi2 > 4.0 do
                      n
                    else
                      iter.(iter, zr2 - zi2 + cr, 2.0 * zr * zi + ci, cr, ci, n + 1, max)
                    end
                end

                palette = for n <- 0..max_iter, do: color.(n, max_iter)

                Enum.reduce(0..(height - 1), Easel.new(width, height), fn py, acc ->
                  ci = y_min + py / height * (y_max - y_min)

                  {runs, start_x, last_n} =
                    Enum.reduce(0..(width - 1), {[], 0, nil}, fn px, {runs, start_x, last_n} ->
                      cr = x_min + px / width * (x_max - x_min)
                      n = iterate.(iterate, 0.0, 0.0, cr, ci, 0, max_iter)

                      cond do
                        is_nil(last_n) -> {runs, px, n}
                        n == last_n -> {runs, start_x, last_n}
                        true -> {[{start_x, px - start_x, last_n} | runs], px, n}
                      end
                    end)

                  runs = [{start_x, width - start_x, last_n} | runs] |> Enum.reverse()

                  Enum.reduce(runs, acc, fn {x, run_w, n}, c ->
                    c
                    |> Easel.set_fill_style(Enum.at(palette, n))
                    |> Easel.fill_rect(x, py, run_w, 1)
                  end)
                end)
                |> Easel.render()
              )

  def render, do: @mandelbrot
end