Packages

Generate multi-size ICO favicon files from PNG images using Elixir binary syntax.

Current section

Files

Jump to
dreamega_ico_encoder lib dreamega_ico_encoder.ex
Raw

lib/dreamega_ico_encoder.ex

defmodule DreamegaIcoEncoder do
@moduledoc """
Generate multi-size ICO favicon files from PNG image data.
The ICO format consists of a 6-byte header, 16-byte directory entries
per image, and concatenated PNG data.
"""
@default_sizes [16, 32, 48, 64, 128, 256]
@doc """
Returns the default icon sizes.
"""
def default_sizes, do: @default_sizes
@doc """
Generate an ICO file from a list of `{size, png_binary}` tuples.
## Parameters
- `png_images` - a list of `{size, png_data}` tuples where `size` is
the pixel dimension and `png_data` is the raw PNG binary.
## Returns
A binary containing the complete ICO file.
## Example
png16 = File.read!("icon-16.png")
png32 = File.read!("icon-32.png")
ico = DreamegaIcoEncoder.generate([{16, png16}, {32, png32}])
File.write!("favicon.ico", ico)
"""
@spec generate([{non_neg_integer(), binary()}]) :: binary()
def generate(png_images) when is_list(png_images) do
count = length(png_images)
# ICO header (6 bytes): reserved=0, type=1, count
header = <<0::little-16, 1::little-16, count::little-16>>
# Data offset base: header(6) + directory entries(16 * count)
data_offset_base = 6 + 16 * count
{directory, _final_offset} =
Enum.reduce(png_images, {<<>>, data_offset_base}, fn {size, png_data}, {dir_acc, offset} ->
w = if size >= 256, do: 0, else: size
h = w
data_size = byte_size(png_data)
entry = <<
w::8,
h::8,
0::8,
0::8,
1::little-16,
32::little-16,
data_size::little-32,
offset::little-32
>>
{dir_acc <> entry, offset + data_size}
end)
# Concatenate all PNG data
image_data =
png_images
|> Enum.map(fn {_size, png_data} -> png_data end)
|> IO.iodata_to_binary()
header <> directory <> image_data
end
end