LibRaw

An Elixir library for native camera RAW decoding on the BEAM, powered by a Rustler NIF that wraps libraw.

Prerequisites

libraw must be installed before compiling or running :libraw:

# macOS
brew install libraw

# Debian / Ubuntu
apt install libraw-dev

LGPL compliance: libraw is dynamically linked at runtime. Distributing your application requires that end users can replace the libraw shared library. See the libraw license for details.

Installation

Add :libraw to your mix.exs dependencies:

def deps do
  [
    {:libraw, "~> 0.1"}
  ]
end

Usage

Decode a RAW file

{:ok, image} = LibRaw.decode("/path/to/photo.CR3")
# => %{pixels: <<...>>, width: 6000, height: 4000, colors: 3, bps: 8}

# 16-bit output with linear gamma
{:ok, image16} = LibRaw.decode("/path/to/photo.CR3",
  output_bps: 16,
  gamma: :linear
)

# Custom gamma curve
{:ok, image_custom} = LibRaw.decode("/path/to/photo.NEF",
  gamma: {2.4, 12.92},
  use_camera_wb: true,
  no_auto_bright: true
)

Options

Option Type Default Description
use_camera_wbbooleantrue Use white balance stored in the file
no_auto_brightbooleanfalse Disable automatic brightening
output_bps8 | 168 Bits per sample in the output
gamma:srgb | :linear | {g0, g1}:srgb Gamma curve

Return value

%{
  pixels: binary(),         # raw pixel bytes (interleaved RGB or RGBA)
  width:  non_neg_integer(),
  height: non_neg_integer(),
  colors: non_neg_integer(), # number of color channels (typically 3)
  bps:    non_neg_integer()  # bits per sample of the output
}

Read metadata without decoding

{:ok, meta} = LibRaw.metadata("/path/to/photo.CR3")
# => %{
#      camera_make:  "Canon",
#      camera_model: "EOS R5",
#      captured_at:  ~U[2023-06-15 10:32:11Z],  # DateTime UTC, or nil
#      iso:          400.0,
#      shutter:      0.002,                        # seconds
#      aperture:     2.8,                          # f-number
#      orientation:  0                             # EXIF flip code
#    }

Architecture

lib/
  lib_raw.ex          Public API: decode/2, metadata/1, gamma resolution, timestamp parsing
  lib_raw/
    nif.ex            use Rustler + NIF stubs (nif_not_loaded fallbacks)
native/
  libraw_nif/
    Cargo.toml        deps: rustler = "0.33"; build-deps: cc = "1", pkg-config = "0.3"
    build.rs          pkg_config::probe("libraw") for dynamic linking; cc::Build compiles wrapper.c
    src/
      lib.rs          rustler::init! and two #[rustler::nif(schedule = "DirtyCpu")] functions
      wrapper.c       thin C shim — C compiler resolves all struct field offsets
      raw.rs          safe Rust RAII wrappers around libraw_data_t / libraw_processed_image_t
      error.rs        LibRawError enum + helpers

Why a C shim?

Direct bindgen / libraw-sys approaches embed struct field offsets at compile time, which can break across libraw versions 0.20, 0.21, and 0.22 as the struct layout evolves. wrapper.c is compiled with the same headers as the installed libraw, so the C compiler always uses the correct offsets. Rust calls only opaque C functions and never touches libraw structs directly.

Dirty CPU Schedulers

Both NIFs (decode_nif and metadata_nif) are annotated with schedule = "DirtyCpu". Decoding a RAW file typically takes 100–500 ms, which is far beyond the 1 ms NIF time budget for normal schedulers. Running on dirty schedulers prevents blocking the BEAM scheduler threads.

License

MIT — see LICENSE.