Oxipng
Elixir bindings for oxipng, a PNG optimizer written in Rust.
Optimize PNG binaries and files, or encode PNGs from raw pixels. Calls are synchronous and use Rustler NIFs on dirty CPU schedulers. The calling process waits while normal BEAM schedulers remain available.
Compression is lossless by default. scale_16: true reduces precision, optimize_alpha: true can change hidden RGB values in transparent pixels, and stripping color metadata can affect image appearance.
Installation
Add the dependency to mix.exs:
{:oxipng, "~> 0.1.0"}
rustler_precompiled downloads NIF binaries from GitHub Releases for these platforms:
| Platform | Architectures |
|---|---|
| Linux (glibc) | x86_64, ARM64, ARM32 hard-float |
| Linux (musl) | x86_64, ARM64 |
| macOS | x86_64, ARM64 |
| Windows | x86_64 |
These binaries do not require Rust. To compile from source, also add {:rustler, "~> 0.38.0"} to your dependencies, install Rust and a C toolchain, and set this before compiling:
export OXIPNG_BUILD=true
Usage
PNG binaries
{:ok, optimized} = Oxipng.optimize(png_binary)
{:ok, optimized} = Oxipng.optimize(png_binary, level: 4, strip: :safe)
Files
# Write to a separate file
{:ok, stats} = Oxipng.optimize_file("input.png", "output.png", level: 3)
# Optimize in place
{:ok, stats} = Oxipng.optimize_file("photo.png", level: 2)
File calls return {:ok, %{in_bytes: input_size, out_bytes: output_size}}.
Empty filenames are rejected. Output is written to a temporary sibling and replaces
the destination only after a successful write. The destination directory must be
writable. Symbolic links are followed; other hard links retain the old contents.
Raw pixels
raw_rgba = <<255, 0, 0, 255, 0, 255, 0, 255>>
{:ok, png} = Oxipng.create_optimized_from_raw(raw_rgba, 2, 1, :rgba, 8)
Color types are :rgba, :rgb, :grayscale, :grayscale_alpha, and {:indexed, palette_binary}. Palettes contain 1–256 RGBA entries of four bytes each, must fit the bit depth, and must define every pixel index.
Grayscale supports bit depths 1, 2, 4, 8, and 16; indexed images support 1, 2, 4, and 8; other types support 8 and 16. Pack each row into whole bytes for depths below 8. Use big-endian samples for 16-bit data.
Each function also has a ! variant that returns the result directly or raises Oxipng.Error.
Options
All three functions accept a keyword list, an atom-keyed map, or an %Oxipng.Options{} struct. Unknown keys and malformed options return errors.
| Option | Default | Description |
|---|---|---|
:level | 2 | Optimization preset from 0 to 6. |
:interlace | nil | true requests Adam7 interlacing; false requests removal. nil or :keep preserves it. Set force: true to apply changes even without a size improvement. |
:strip | :none | Metadata stripping policy; see below. |
:optimize_alpha | false | Allow RGB values of fully transparent pixels to change. |
:bit_depth_reduction | true | Attempt bit depth reduction. |
:color_type_reduction | true | Attempt color type reduction. |
:palette_reduction | true | Attempt palette reduction. |
:grayscale_reduction | true | Attempt grayscale reduction. |
:idat_recoding | true | Recode IDAT chunks. Reductions can require recoding even when this is false. |
:scale_16 | false | Allow lossy 16-bit to 8-bit scaling when bit depth reduction is enabled. |
:deflater | nil | Use the preset default, {:libdeflater, 0..12}, :zopfli, {:zopfli, iterations}, or {:zopfli, iterations, without_improvement}. |
:filters | nil | Use the preset default or a list of filters described below. |
:fast_evaluation | nil | Use the preset default, or set true/false to control fast filter evaluation. |
:timeout | nil | Soft budget in milliseconds. Skips further work after the deadline, but does not interrupt running compression. |
:max_decompressed_size | nil | Maximum decompressed input IDAT size in bytes. |
:force | false | Return or write output even if it is not smaller than the input. |
:fix_errors | false | Attempt to recover from decoding errors. |
:preserve_attrs | false | For files, preserve permissions and modification time, but not access time. |
Stripping policies:
:noneorfalse: disable optional metadata stripping.:safeortrue: use oxipng's allowlist (cICP,iCCP,sRGB,pHYs,acTL,fcTL,fdAT). This removesgAMAandcHRM, which can affect appearance.:all: strip all optional metadata, including color profiles.{:keep, chunks}: keep only the specified optional chunks.{:strip, chunks}: strip the specified chunks.
Chunk names are four-byte strings or atoms, such as "tEXt" or :tEXt.
A custom filter list must be nonempty. Filters are :none, :sub, :up, :average, :paeth, :min_sum, :entropy, :bigrams, :big_ent, or {:brute, lines, level}. Brute filtering takes a positive line count and a compression level from 1 to 12.
Releasing
The review fixes retain version 0.1.0 for replacement of the initial release.
Until its rebuilt archives and checksums are published, compile this checkout
with OXIPNG_BUILD=true.
- Keep the versions in
mix.exsandnative/oxipng_nif/Cargo.tomlaligned and commit the updated Cargo lockfile. - Push the changes. The release workflow builds all archives, generates their
checksums, and tests the precompiled Linux artifact. Tag pushes publish the
verified archives, checksum manifest, and Hex package to GitHub Releases.
To replace the existing release from the updated branch, run the workflow
manually with
publishenabled; it replaces assets for the project version. - Copy the generated
checksum-Elixir.Oxipng.Native.exsinto the checkout before building a package manually. Always use the checksums of the rebuilt archives. The Hex archive produced by the workflow already contains the correct manifest. - Replace the initial Hex package with
OXIPNG_BUILD=true mix hex.publish --replacewhile replacement is permitted. Hex publishing is a separate step from the GitHub release workflow.
The checked-in manifest is empty while the replacement is being prepared. Source builds work without it; precompiled installations require the generated manifest.
License
MIT.