# Rxing

[![CI](https://github.com/ivan-podgurskiy/rxing/actions/workflows/ci.yml/badge.svg)](https://github.com/ivan-podgurskiy/rxing/actions/workflows/ci.yml)
[![Hex.pm](https://img.shields.io/hexpm/v/rxing.svg)](https://hex.pm/packages/rxing)
[![HexDocs](https://img.shields.io/badge/hexdocs-rxing-blue.svg)](https://hexdocs.pm/rxing)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

`rxing` decodes barcodes from image binaries using a source-built Rustler NIF
powered by the Rust `rxing` crate.

## Installation

```elixir
def deps do
  [
    {:rxing, "~> 0.1.0"}
  ]
end
```

`rxing` compiles its native library from source when the dependency is
compiled. Install Rust 1.91 or newer (`rustc` and `cargo`) before running
`mix compile`.

## Immediate Example (binary input)

```elixir
image = File.read!("path/to/barcode.png")

{:ok, [%Rxing.Result{format: :qr_code} | _results]} =
  Rxing.decode(image, formats: [:qr_code])
```

## Public APIs

`Rxing` exposes three decoding entry points:

- `Rxing.decode/2` decodes every supported barcode in an image binary.
- `Rxing.decode_one/2` decodes the first supported barcode or returns `{:error, :not_found}`.
- `Rxing.decode_file/2` reads an image from a path and decodes it.

All APIs share the same option vocabulary and stable error vocabulary.

## Decoding Binaries, One Result, and Files

```elixir
image = File.read!("path/to/barcodes.png")

# Decode every result up to the configured bound.
{:ok, results} =
  Rxing.decode(image, formats: [:qr_code, :data_matrix], max_results: 32)

# Decode only the first result.
{:ok, first} = Rxing.decode_one(image, formats: [:qr_code, :data_matrix])
%Rxing.Result{} = first

# Let Rxing read a regular file after applying the input-size pre-check.
{:ok, file_results} =
  Rxing.decode_file("path/to/barcodes.png", formats: [:qr_code, :data_matrix])
```

`results` and `file_results` are lists of `%Rxing.Result{}` values in upstream
detection order. When no barcode is recognized, decoding returns `{:ok, []}`.

## Supported Barcode Formats

`Rxing.formats/0` returns the guaranteed format set in stable order:

- `:qr_code`
- `:data_matrix`
- `:code_128`
- `:ean_13`

## Supported Image Encodings

The binary API accepts these content-detectable formats from `image` 0.25:

- BMP
- DDS
- OpenEXR
- farbfeld
- GIF
- HDR
- ICO
- JPEG
- PNG
- PNM
- QOI
- TIFF
- WebP

Committed integration fixtures guarantee representative decoding coverage for
PNG, JPEG, GIF, BMP, and WebP. Other enabled encodings use the same content-based
classification and decoding pipeline.

AVIF and TGA are not accepted by the current binary-only pipeline: AVIF decoding
requires the separate `image/avif-native` feature, while `image::guess_format`
cannot identify TGA without a filename extension.

## Options, Filtering, and Defaults

Public options:

- `:formats` (default: `[:qr_code, :data_matrix, :code_128, :ean_13]`)
- `:try_harder` (default: `false`)
- `:try_inverted` (default: `false`)
- `:max_results` (default: `32`, allowed range `1..256`)
- `:max_input_bytes` (default: `20 * 1024 * 1024`)
- `:max_width` (default: `10_000`)
- `:max_height` (default: `10_000`)
- `:max_pixels` (default: `40_000_000`)
- `:max_alloc_bytes` (default: `256 * 1024 * 1024`)

Filtering examples:

- Restrict formats: `formats: [:qr_code]`
- Enable rotated scan retries: `try_harder: true`
- Enable inverted scan retries: `try_inverted: true`
- Raise result bound for dense images: `max_results: 64`

## Stable Errors

Decode operations return:

- `{:error, :invalid_image}`
- `{:error, :internal_error}`
- `{:error, {:unsupported_format, format}}`
- `{:error, {:invalid_option, key}}`
- `{:error, {:limit_exceeded, limit}}`
- `{:error, {:file, reason}}` (`decode_file/2`)
- `{:error, :not_found}` (`decode_one/2`)

## Result Payload, Raw Bytes, and Metadata

Every successful decode returns `%Rxing.Result{}` values:

- `:format` - one of the supported barcode format atoms.
- `:text` - decoded UTF-8 text when available, else `nil`.
- `:raw_bytes` - raw decoded bytes when available, else `nil`.
- `:points` - list of `%{x: float(), y: float()}` result points.
- `:metadata` - decoder metadata map. Supported keys include:
  `:orientation`, `:byte_segments`, `:error_correction_level`,
  `:structured_append_sequence`, `:structured_append_parity`,
  `:symbology_identifier`, `:is_inverted`, and `:is_mirrored`.

## Runtime and Concurrency Notes

- Native decoding runs on dirty CPU schedulers to avoid monopolizing normal BEAM schedulers.
- Callers remain responsible for choosing safe request-level concurrency and backpressure limits.

## Source Build Requirements and Platforms

- Elixir: `~> 1.15`.
- Tested runtime pairs: Elixir 1.15.8 / OTP 26 and Elixir 1.20 / OTP 29.
- Rust toolchain: `rustc`/`cargo` 1.91 or newer.
- The native crate is built from source during dependency compilation.
- Supported CI platforms: Linux and macOS.

## Benchmarks

From a repository checkout with development dependencies installed, run:

```console
mix run bench/rxing_bench.exs
```

The non-gating Benchee report includes throughput, median, p95, and p99 latency,
BEAM memory, scheduler counts, run-queue snapshots, and heartbeat observations.
It covers the guaranteed barcode formats, clean/rotated/inverted/damaged/multiple
and no-code scenarios, an image-size sweep, and concurrency levels 1 through 32.

Treat the output as a baseline for comparisons made on the same host and
toolchain. It deliberately contains no pass/fail latency thresholds or portable
speed claims.

## Non-goals (0.1.0)

- Barcode encoding/generation APIs.
- Precompiled NIF artifacts in Hex packages.
- Alternative native backends beyond Rust `rxing`.

## License

MIT. See [LICENSE](LICENSE).
