Image to gameboy tile conversion
Mix.install([
{:ex_png, "~> 1.0"},
{:kino, "~> 0.9.4"},
{:nx, "~> 0.5.3"}
])
Convert image to 2 bit grayscale
# data = img.pixels |> List.flatten() |> Stream.map(fn <> -> trunc((r+g+b) / 3) end) |> Stream.map(fn value -> trunc(value / 255 * 4) end) |> Stream.chunk_every(4) |> Enum.reduce(<<>>, fn [a,b,c,d], acc -> acc <> <> end)
width = 160
height = 144
file_input = Kino.Input.image("Image", format: :rgb, size: {height, width}, fit: :crop)
image = Kino.Input.read(file_input)
grayscale =
image.data
|> :binary.bin_to_list()
|> Enum.chunk_every(3)
|> Enum.map(fn [r, g, b] -> 3 - trunc((r + g + b) / 3 / 200 * 3) end)
Enum.uniq(grayscale)
grayscale |> Nx.tensor() |> Nx.reshape({height, width}) |> Nx.to_heatmap()
Convert grayscale image to tile data
tile_size = 8
tiles_per_row = div(width, tile_size)
rows = grayscale |> Enum.chunk_every(tiles_per_row * tile_size * tile_size)
length(rows)
length(Enum.at(rows, 0))
tiles =
rows
|> Enum.flat_map(fn row ->
tile_rows = row |> Enum.chunk_every(8)
Enum.map(0..(tiles_per_row - 1), fn offset ->
tile_rows |> Enum.drop(offset) |> Enum.take_every(tiles_per_row)
end)
end)
length(tiles)
Convert tile data to 2bpp
import Bitwise
tile_bytes =
tiles
# |> Enum.take(1)
|> Enum.map(fn rows ->
Enum.map(rows, fn row ->
row
|> Enum.with_index()
|> Enum.reduce([0, 0], fn {pixel, i}, [lo, hi] ->
case pixel do
0 -> [lo, hi]
1 -> [lo ||| 1 <<< (7 - i), hi]
2 -> [lo, hi ||| 1 <<< (7 - i)]
3 -> [lo ||| 1 <<< (7 - i), hi ||| 1 <<< (7 - i)]
end
end)
end)
end)
IO.iodata_length(tile_bytes)
IO.puts(inspect(tile_bytes |> IO.iodata_to_binary(), limit: :infinity))