Qiroex

Qiroex

A pure-Elixir QR code generator — zero dependencies, full spec, beautiful output.

Hex.pm VersionDocumentationCILicense

Installation · Quick Start · Styling · Logos · Payloads · API


Qiroex generates valid, scannable QR codes entirely in Elixir with no external dependencies — no C NIFs, no system libraries, no ImageMagick. It implements the full ISO 18004 specification and outputs to SVG, PNG, and terminal.

Features

Installation

Add qiroex to your list of dependencies in mix.exs:

def deps do
  [
    {:qiroex, "~> 0.1.0"}
  ]
end

Then run mix deps.get.

Quick Start

Generate and save an SVG

Qiroex.save_svg("https://example.com", "qr.svg")
Basic QR code

Generate and save a PNG

Qiroex.save_png("https://example.com", "qr.png")

Print to terminal

Qiroex.print("Hello from Qiroex!")

Work with raw data

# Get an SVG string
{:ok, svg} = Qiroex.to_svg("Hello")

# Get a PNG binary
{:ok, png} = Qiroex.to_png("Hello")

# Get a QR struct for inspection
{:ok, qr} = Qiroex.encode("Hello")
Qiroex.info(qr)
# => %{version: 1, ec_level: :m, mode: :byte, mask: 4, modules: 21, data_bytes: 5}

# Get the raw 0/1 matrix
{:ok, matrix} = Qiroex.to_matrix("Hello")

Encoding Options

Control the encoding process with these options (available on all functions):

# Error correction level (:l, :m, :q, :h)
Qiroex.save_svg("Hello", "qr.svg", level: :h)

# Force a specific version (1–40)
Qiroex.save_svg("Hello", "qr.svg", version: 5)

# Force encoding mode
Qiroex.save_svg("12345", "qr.svg", mode: :numeric)

# Force mask pattern (0–7)
Qiroex.save_svg("Hello", "qr.svg", mask: 2)

# Combine freely
Qiroex.save_svg("Hello", "qr.svg", level: :q, version: 3, mask: 0)

Render Options

SVG Options

Qiroex.save_svg("Hello", "qr.svg",
  module_size: 12,             # pixel size of each module (default: 10)
  quiet_zone: 2,               # modules of white border (default: 4)
  dark_color: "#4B275F",       # any CSS color
  light_color: "#F4F1F6"       # background color
)
Custom colors

PNG Options

Qiroex.save_png("Hello", "qr.png",
  module_size: 20,                   # pixel size per module (default: 10)
  quiet_zone: 3,                     # quiet zone modules (default: 4)
  dark_color: {75, 39, 95},          # {r, g, b} tuple, 0–255
  light_color: {244, 241, 246}       # background color
)

Styling

Qiroex supports rich visual customization through the Qiroex.Style struct. All style options apply to SVG output; PNG supports finder pattern colors.

Module Shapes

Choose how individual data modules are rendered:

# Circular dots
style = Qiroex.Style.new(module_shape: :circle)
Qiroex.save_svg("Hello", "circles.svg", style: style)

# Rounded squares
style = Qiroex.Style.new(module_shape: :rounded, module_radius: 0.4)
Qiroex.save_svg("Hello", "rounded.svg", style: style)

# Diamond (rotated squares)
style = Qiroex.Style.new(module_shape: :diamond)
Qiroex.save_svg("Hello", "diamond.svg", style: style)

# Leaf (asymmetric rounded corners)
style = Qiroex.Style.new(module_shape: :leaf)
Qiroex.save_svg("Hello", "leaf.svg", style: style)

# Shield (flat top, curved pointed bottom)
style = Qiroex.Style.new(module_shape: :shield)
Qiroex.save_svg("Hello", "shield.svg", style: style)
Circle modules
:circle
Rounded modules
:rounded
Diamond modules
:diamond
Leaf modules
:leaf
Shield modules
:shield

Finder Pattern Colors

Customize the three concentric layers of each finder pattern independently:

style = Qiroex.Style.new(
  module_shape: :rounded,
  module_radius: 0.3,
  finder: %{
    outer: "#E63946",    # 7×7 dark border ring
    inner: "#F1FAEE",    # 5×5 light ring
    eye:   "#1D3557"     # 3×3 dark center
  }
)

Qiroex.save_svg("Hello", "finder.svg", style: style)
Finder pattern colors

Finder Pattern Shapes

Customize the shape of each finder pattern layer independently. Finder layers are rendered as single compound SVG elements for clean visual output. Available shapes: :square, :rounded, :circle, :diamond, :leaf, :shield.

# Circle finders with rounded data modules
style = Qiroex.Style.new(
  module_shape: :rounded,
  module_radius: 0.3,
  finder: %{
    outer: "#E63946",  outer_shape: :rounded,
    inner: "#F1FAEE",  inner_shape: :square,
    eye:   "#1D3557",  eye_shape: :circle
  }
)

Qiroex.save_svg("Hello", "finder_shapes.svg", style: style)

You can also set shapes without custom colors — the default dark/light colors will be used:

style = Qiroex.Style.new(
  finder: %{
    outer_shape: :rounded,
    inner_shape: :rounded,
    eye_shape: :circle
  }
)
Rounded finders
Rounded
Circle finders
Circle
Leaf finders
Leaf
Shield finders
Shield
Mixed finders
Mixed shapes

Gradient Fills

Apply linear or radial gradients to dark modules:

# Linear gradient at 135°
style = Qiroex.Style.new(
  module_shape: :circle,
  gradient: %{
    type: :linear,
    start_color: "#667EEA",
    end_color: "#764BA2",
    angle: 135
  }
)

Qiroex.save_svg("Hello", "gradient.svg", style: style)
Linear gradient
Linear
Radial gradient
Radial
Full styled
Combined

Kitchen Sink

Combine everything for maximum visual impact:

style = Qiroex.Style.new(
  module_shape: :circle,
  finder: %{outer: "#2D3436", inner: "#FFFFFF", eye: "#E17055"},
  gradient: %{type: :linear, start_color: "#2D3436", end_color: "#636E72", angle: 45}
)

Qiroex.save_svg("https://elixir-lang.org", "styled.svg", style: style)

Logo Embedding

Embed a logo in the center of your QR code. Qiroex supports both SVG markup and raster images (PNG, JPEG, WEBP, GIF, BMP, AVIF, TIFF) — all with zero dependencies. It automatically clears the modules behind the logo area and validates that the logo doesn't exceed the error correction capacity.

SVG Logo

logo = Qiroex.Logo.new(
  svg: ~s(<svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="40" fill="#9B59B6"/>
    <text x="50" y="62" text-anchor="middle" font-size="36"
          font-weight="bold" fill="white" font-family="sans-serif">Ex</text>
  </svg>),
  size: 0.22,          # 22% of QR code size
  shape: :circle,      # background shape (:square, :rounded, :circle)
  padding: 1           # padding in modules around the logo
)

# Use high EC level (:h) for best scan reliability with logos
Qiroex.save_svg("https://elixir-lang.org", "logo.svg", level: :h, logo: logo)

Raster Image Logo (PNG, JPEG, WEBP, ...)

Load any image file and embed it directly — the format is auto-detected from the binary:

logo = Qiroex.Logo.new(
  image: File.read!("company_logo.png"),
  size: 0.22,
  shape: :circle,
  padding: 1
)

Qiroex.save_svg("https://example.com", "branded.svg", level: :h, logo: logo)

Raster images are embedded as base64 data URIs inside the SVG — no external files or dependencies needed. You can also specify the format explicitly:

Qiroex.Logo.new(image: jpeg_bytes, image_type: :jpeg, size: 0.2)
Logo embedding
SVG Logo
Styled + Logo
Styled + Logo
PNG Logo
PNG Logo

Logo + Style

Logos work seamlessly with all styling options:

style = Qiroex.Style.new(
  module_shape: :rounded,
  module_radius: 0.3,
  finder: %{outer: "#4B275F", inner: "#FFFFFF", eye: "#9B59B6"}
)

Qiroex.save_svg("https://elixir-lang.org", "branded.svg",
  level: :h, style: style, logo: logo)

Logo Options

Option Default Description
:svg SVG markup string (provide :svgor:image)
:image Binary image data: PNG, JPEG, WEBP, GIF, BMP (provide :imageor:svg)
:image_typeauto-detected Image format: :png, :jpeg, :webp, :gif, :bmp
:size0.2 Logo size as fraction of QR code (0.0–0.4)
:padding1 Padding around logo in modules
:background"#ffffff" Background color behind the logo
:shape:square Background shape: :square, :rounded, :circle
:border_radius4 Corner radius for :rounded shape

Coverage Validation

Qiroex automatically validates that the logo doesn't cover too many modules. If the logo is too large for the chosen error correction level, you'll get a clear error message:

large_logo = Qiroex.Logo.new(svg: "<svg/>", size: 0.4)

{:error, message} = Qiroex.to_svg("Hello", level: :l, logo: large_logo)
# => "Logo covers 28.3% of modules, but EC level :l safely supports only 5.6%.
#     Use a higher EC level or a smaller logo size."

Tip: Always use error correction level :h when embedding logos for maximum scan reliability.

Payload Builders

Generate structured data payloads for common QR code use cases with a single function call:

# WiFi network — scan to connect
{:ok, svg} = Qiroex.payload(:wifi,
  [ssid: "CoffeeShop", password: "latte2024"],
  :svg, dark_color: "#2C3E50")

Qiroex ships with 11 payload builders covering the most common QR code use cases:

WiFi

Scan to auto-connect to a network.

{:ok, svg} = Qiroex.payload(:wifi,
  [ssid: "MyNetwork", password: "secret123", auth: :wpa],
  :svg)
WiFi QR

URL

Open a website in the browser.

{:ok, svg} = Qiroex.payload(:url,
  [url: "https://elixir-lang.org"],
  :svg)
URL QR

Email

Compose an email with pre-filled fields.

{:ok, svg} = Qiroex.payload(:email,
  [to: "hello@example.com", subject: "Hi!", body: "Nice to meet you."],
  :svg)
Email QR

SMS

Open the messaging app with a pre-filled text.

{:ok, svg} = Qiroex.payload(:sms,
  [number: "+1-555-0123", message: "Hello!"],
  :svg)
SMS QR

Phone

Initiate a phone call.

{:ok, svg} = Qiroex.payload(:phone,
  [number: "+1-555-0199"],
  :svg)
Phone QR

Geo Location

Open a map to a specific location.

{:ok, svg} = Qiroex.payload(:geo,
  [latitude: 48.8566, longitude: 2.3522, query: "Eiffel Tower"],
  :svg)
Geo QR

vCard

Share a full contact card.

{:ok, svg} = Qiroex.payload(:vcard,
  [first_name: "Jane", last_name: "Doe",
   phone: "+1-555-0199", email: "jane@example.com",
   org: "Acme Corp", title: "Engineer"],
  :svg)
vCard QR

vEvent

Add a calendar event.

{:ok, svg} = Qiroex.payload(:vevent,
  [summary: "Team Standup",
   start: ~U[2026-03-01 09:00:00Z],
   end: ~U[2026-03-01 09:30:00Z],
   location: "Conference Room A"],
  :svg)
vEvent QR

MeCard

Share a contact (simpler alternative to vCard, popular on mobile).

{:ok, svg} = Qiroex.payload(:mecard,
  [name: "Doe,Jane", phone: "+1-555-0199", email: "jane@example.com"],
  :svg)
MeCard QR

Bitcoin

Request a Bitcoin payment (BIP-21).

{:ok, svg} = Qiroex.payload(:bitcoin,
  [address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
   amount: 0.001, label: "Donation"],
  :svg)
Bitcoin QR

WhatsApp

Open a WhatsApp chat with a pre-filled message.

{:ok, svg} = Qiroex.payload(:whatsapp,
  [number: "+1234567890", message: "Hello from Qiroex!"],
  :svg)
WhatsApp QR

The third argument is the output format: :svg, :png, :terminal, :matrix, or :encode.

Error Handling

All functions return {:ok, result} / {:error, reason} tuples. Bang variants raise ArgumentError:

# Safe — returns error tuple
{:error, message} = Qiroex.encode("")
# => "Data cannot be empty"

{:error, message} = Qiroex.to_svg("test", level: :x)
# => "invalid error correction level: :x. Must be one of [:l, :m, :q, :h]"

{:error, message} = Qiroex.to_png("test", dark_color: "#000")
# => "invalid dark_color: \"#000\". Must be an {r, g, b} tuple with values 0–255"

# Bang — raises on error
svg = Qiroex.to_svg!("Hello")        # returns SVG string directly
png = Qiroex.to_png!("Hello")        # returns PNG binary directly
qr  = Qiroex.encode!("Hello")        # returns QR struct directly

API Reference

Core Functions

Function Description
Qiroex.encode(data, opts) Encode data into a %Qiroex.QR{} struct
Qiroex.to_svg(data, opts) Generate SVG string
Qiroex.to_png(data, opts) Generate PNG binary
Qiroex.to_terminal(data, opts) Generate terminal-printable string
Qiroex.to_matrix(data, opts) Generate 2D list of 0/1
Qiroex.save_svg(data, path, opts) Write SVG to file
Qiroex.save_png(data, path, opts) Write PNG to file
Qiroex.print(data, opts) Print QR code to terminal
Qiroex.payload(type, opts, format) Generate payload QR code
Qiroex.info(qr) Get metadata about an encoded QR

All functions have bang (!) variants that raise instead of returning error tuples.

Encoding Options

Option Values Default Description
:level:l, :m, :q, :h:m Error correction level
:version140, :auto:auto QR version (size)
:mode:numeric, :alphanumeric, :byte, :kanji, :auto:auto Encoding mode
:mask07, :auto:auto Mask pattern

SVG Render Options

Option Type Default Description
:module_size integer 10 Pixel size of each module
:quiet_zone integer 4 Quiet zone border in modules
:dark_color string "#000000" CSS color for dark modules
:light_color string "#ffffff" CSS color for background
:style%Style{}nil Visual styling configuration
:logo%Logo{}nil Center logo configuration

PNG Render Options

Option Type Default Description
:module_size integer 10 Pixel size of each module
:quiet_zone integer 4 Quiet zone border in modules
:dark_color{r,g,b}{0,0,0} RGB tuple for dark modules
:light_color{r,g,b}{255,255,255} RGB tuple for background
:style%Style{}nil Finder pattern colors

Architecture

Qiroex implements the full QR code pipeline from scratch:

Data → Mode Detection → Version Selection → Bit Encoding
    → Reed-Solomon EC → Interleaving → Matrix Placement
    → Masking (8 patterns × 4 penalty rules) → Format Info
    → Render (SVG / PNG / Terminal)

Key implementation details:

License

MIT License. See LICENSE for details.