エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

ElixirでPNGを出力するための覚書

defmodule PNG do
  @moduledoc """
  PNG イメージを作成します

  cf. https://en.wikipedia.org/wiki/Portable_Network_Graphics
  """

  @magic_number [0x89, "PNG", 0x0D, 0x0A, 0x1A, 0x0A]

  @width 32
  @height 8

  @depth 8
  @color_type 3 # indexed

  @compression_method 0
  @filter_method 0
  @interlace_method 0

  @header <<
    @width::32,
    @height::32,
    @depth,
    @color_type,
    @compression_method,
    @filter_method,
    @interlace_method
  >>

  @palette [
    0x00, 0x00, 0x00, # パレット番号0 - R:0, G:0, B:0
    0xFF, 0xFF, 0xFF  # パレット番号1 - R:255, G:255, B:255
  ]

  @image [
    [ 0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0,
      1, 0, 0, 1,   0, 0, 0, 0,   0, 0, 0, 0,   0, 0, 0, 0 ],
    [ 0, 1, 1, 0,   0, 1, 1, 0,   1, 0, 0, 0,   1, 1, 0, 0,
      1, 0, 0, 1,   0, 0, 1, 1,   0, 0, 1, 1,   0, 1, 1, 0 ],
    [ 1, 0, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,
      1, 1, 1, 1,   1, 1, 0, 0,   0, 1, 0, 1,   0, 1, 0, 1 ],
    [ 1, 0, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 0,
      1, 0, 0, 1,   0, 1, 0, 0,   0, 1, 0, 1,   0, 1, 0, 1 ],
    [ 1, 1, 1, 1,   0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 0,
      1, 0, 0, 1,   0, 0, 1, 0,   0, 1, 0, 1,   0, 1, 0, 1 ],
    [ 1, 0, 0, 0,   0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 0,
      1, 0, 0, 1,   0, 0, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1 ],
    [ 1, 0, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1,   0, 1, 0, 0,
      1, 0, 0, 1,   0, 0, 0, 1,   0, 1, 0, 1,   0, 1, 0, 1 ],
    [ 0, 1, 1, 0,   0, 1, 0, 1,   0, 1, 0, 0,   1, 0, 1, 0,
      0, 1, 0, 0,   1, 1, 1, 0,   0, 0, 1, 0,   1, 1, 0, 1 ]
  ]

  @scanline_filter_none 0

  def write_image(filename) do
    rows =
      Enum.map(@image, fn row ->
        [@scanline_filter_none, row]
      end)

    File.open(filename, [:write], fn io ->
      IO.binwrite(io, @magic_number)
      write_chunk(io, "IHDR", @header)
      write_chunk(io, "PLTE", @palette)
      write_chunk(io, "IDAT", compress(rows))
      write_chunk(io, "IEND", "")
    end)
  end

  def write_chunk(io, type, data) do
    length = :erlang.iolist_size(data)
    crc = :erlang.crc32([type, data])

    IO.binwrite(io, <<length::big-size(32)>>)
    IO.binwrite(io, type)
    IO.binwrite(io, data)
    IO.binwrite(io, <<crc::big-size(32)>>)
  end

  @doc """
  Erlang の zlib モジュールを利用してデータを圧縮します

  see http://erlang.org/doc/man/zlib.html
  """
  def compress(data) do
    zip = :zlib.open()
    :ok = :zlib.deflateInit(zip)
    compressed = :zlib.deflate(zip, data, :finish)
    :ok = :zlib.deflateEnd(zip)
    :ok = :zlib.close(zip)

    compressed
  end
end
$ elixirc png.ex
$ elixir -e 'PNG.write_image("emattsan.png")'