README.md

# QQR

QR code encoder and decoder in pure Elixir. Zero dependencies — no NIFs, no ports, no C.

## Installation

```elixir
def deps do
  [{:qqr, "~> 0.2.0"}]
end
```

## Encoding

```elixir
{:ok, matrix} = QQR.encode("Hello World")
{:ok, matrix} = QQR.encode("12345", ec_level: :high, mode: :numeric)
```

Options: `:ec_level` (`:low`, `:medium`, `:quartile`, `:high`), `:mode` (`:numeric`, `:alphanumeric`, `:byte`, `:auto`), `:version` (1–40), `:mask` (0–7). All default to auto.

### SVG

```elixir
svg = QQR.to_svg("https://example.com")
svg = QQR.to_svg("Hello", dot_shape: :rounded, color: "#336699")
```

Styling: `:dot_shape` (`:square`, `:rounded`, `:dots`, `:diamond`), `:finder_shape` (`:square`, `:rounded`, `:dots`), `:dot_size`, `:module_size`, `:quiet_zone`, `:color`, `:background`, `:logo`. See `QQR.SVG` for details.

### Phoenix LiveView

```heex
<div class="qr"><%= raw(QQR.to_svg_iodata(@url, dot_shape: :rounded)) %></div>
```

`to_svg_iodata/2` returns iodata — no extra binary copy, sent directly to the socket.

### PNG with stb_image

```elixir
{:ok, matrix} = QQR.encode("Hello World")
dim = matrix.width
scale = 10
quiet = 4
img_dim = (dim + quiet * 2) * scale

rgb =
  for y <- 0..(img_dim - 1), x <- 0..(img_dim - 1), into: <<>> do
    qr_x = div(x, scale) - quiet
    qr_y = div(y, scale) - quiet

    if QQR.BitMatrix.get(matrix, qr_x, qr_y),
      do: <<0, 0, 0>>,
      else: <<255, 255, 255>>
  end

%StbImage{data: rgb, shape: {img_dim, img_dim, 3}, type: {:u, 8}}
|> StbImage.write_file!("qr.png")
```

## Decoding

### From RGBA pixels

```elixir
case QQR.decode(rgba_binary, width, height) do
  {:ok, result} ->
    result.text     #=> "https://example.com"
    result.version  #=> 3
    result.bytes    #=> [104, 116, 116, 112, ...]
    result.chunks   #=> [%QQR.Chunk{mode: :byte, text: "https://example.com", bytes: [...]}]
    result.location #=> %QQR.Location{top_left_corner: {10.5, 10.5}, ...}

  :error ->
    # no QR code found
end
```

`rgba_binary` is a binary of RGBA pixels — 4 bytes per pixel, same format as `ImageData` in browsers.

### From a file with stb_image

```elixir
{:ok, img} = StbImage.read_file("photo.png")
{h, w, c} = img.shape

rgba =
  case c do
    4 -> img.data
    3 -> for <<r, g, b <- img.data>>, into: <<>>, do: <<r, g, b, 255>>
  end

case QQR.decode(rgba, w, h) do
  {:ok, result} -> result.text
  :error -> "no QR code found"
end
```

### From a module grid

Skip image processing when you already have a binarized grid:

```elixir
QQR.decode_matrix(bit_matrix)
```

### Inversion

By default both normal and inverted (light-on-dark) images are tried. Pass `inversion: :dont_invert` for ~2× speedup when you know the background is white.

## Features

- Versions 1–40, all error correction levels (L/M/Q/H)
- Numeric, alphanumeric, and byte encoding/decoding modes
- Kanji decoding (raw bytes — Shift-JIS to text conversion not yet implemented)
- ECI segment parsing (designators consumed, encoding not applied)
- Reed-Solomon error correction (encode and decode)
- Adaptive binarization, perspective correction
- Dark-background (inverted) and mirror/transposed QR codes
- SVG rendering with dot shapes (square, rounded, dots, diamond), finder pattern styling, and logo embedding

## Benchmarks

Compared against [qrex](https://hex.pm/packages/qrex) (Rust NIF, PNG input). Run with `elixir bench/decode.exs`.

| Input | QQR.decode_matrix | QRex (Rust NIF) | QQR.decode (RGBA) |
|-------|------------------:|----------------:|------------------:|
| Version 1, "Hello" | **30 µs** | 51 µs | 1.5 ms |
| Version 2, URL | **55 µs** | 70 µs | 2.1 ms |
| Version 6, 100 chars | 251 µs | **146 µs** | 5.5 ms |

Grid-only decode (`decode_matrix`) is **1.3–1.7× faster than Rust** for small and medium QR codes. The full RGBA pipeline is slower due to image processing overhead in the binarizer and locator.

## How it works

```
Encode: text → data bits → RS error correction → matrix → mask → QR
Decode: RGBA → binarize → locate → extract → unmask → RS correct → text
```

GF(256) exp/log tables are compiled into pattern-matched function heads. The `BitMatrix` uses a flat tuple with `:erlang.element/2` for constant-time access. No mutable state — zigzag traversal, Bresenham walks, and polynomial arithmetic are purely functional.

Encoder ported from [etiket](https://github.com/productdevbook/etiket). Decoder ported from [jsQR](https://github.com/cozmo/jsQR) with algorithm verification against [quirc](https://github.com/dlbeer/quirc).

## License

[MIT](LICENSE)