Skip to content

dannote/qqr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

QQR

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

Installation

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

Encoding

{: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

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

<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

{: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

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

{: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:

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 (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. Decoder ported from jsQR with algorithm verification against quirc.

License

MIT

About

QR code encoder and decoder in pure Elixir

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages