Skip to content

Commit

Permalink
feat: error correction level (#1)
Browse files Browse the repository at this point in the history
* feat: add support for error correction

also includes a refactor to use an option struct for input and enum
values for format and error correction

* feat: error correction docs and new qr! function

* docs: README and error correction examples

* increment version to 0.1.4
  • Loading branch information
nbw committed May 30, 2022
1 parent 841094d commit 281f5e6
Show file tree
Hide file tree
Showing 15 changed files with 235 additions and 81 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ The following QR formats are supported:
- base64 PNG (`:png64`)
- base64 JPG (`:jpg64`)

### Options

- width: width in pixels (default: 200)
- height: height in pixels (default: 200)
- size: shorthand for width and height (default: 200)
- ec: [error correction level](https://docs.rs/qrcode/0.6.0/qrcode/types/enum.EcLevel.html#variants) `:l`, `:m`, `:q`, `:h` (default: :m)

### SVG

```elixir
Expand Down Expand Up @@ -68,6 +75,12 @@ File.write("./assets/qr.jpg", binary)
| PNG64 | [ sample ](assets/base65.html) |
| JPG64 | -- |

Error correction:

| L | M | Q | H |
| -------------------------- | -------------------------- | -------------------------- | -------------------------- |
| ![ l ](assets/qr_ec_l.jpg) | ![ m ](assets/qr_ec_m.jpg) | ![ q ](assets/qr_ec_q.jpg) | ![ h ](assets/qr_ec_h.jpg) |

## Benchmarks

Benchmarks have been included to compare Qrusty (Rust based) to [EQRCode](https://github.com/SiliconJungles/eqrcode) (Elixir based), as it's the defacto Elixir QR Code library.
Expand Down Expand Up @@ -106,11 +119,8 @@ You will need the following installed:
- Rust
- [Cross](https://github.com/cross-rs/cross)

### Compiling QRusty

```
mix compile.local
```
1. Increment the version in mix.exs so that the build on github can no longer be found
2. Compile locally: `mix compile.local` or `QRUSTY_BUILD=true iex -S mix`

## Contributions

Expand Down
Binary file added assets/qr_ec_h.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/qr_ec_l.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/qr_ec_m.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/qr_ec_q.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 53 additions & 11 deletions lib/qrusty.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ defmodule Qrusty do
- base64 PNG (`:png64`)
- base64 JPG (`:jpg64`)
### Options
- *width*: width in pixels (default: 200)
- *height*: height in pixels (default: 200)
- *size*: shorthand for width and height (default: 200)
- *ec*: [error correction level](https://docs.rs/qrcode/0.6.0/qrcode/types/enum.EcLevel.html#variants) `:l`, `:m`, `:q`, `:h` (default: :m)
### SVG
```elixir
> {:ok, %Qrusty.QR{encoded_data: svg}} = Qrusty.qr("https://elixir-lang.org/", :svg, size: 200)
> {:ok, %Qrusty.QR{encoded_data: svg}} = Qrusty.qr("https://elixir-lang.org/", :svg, size: 200, ec: :h)
File.write("./assets/qr.svg", svg)
```
Expand Down Expand Up @@ -48,18 +54,25 @@ defmodule Qrusty do
</a>
```
| Format | Sample |
| ------ | ----------------------------------------------------------------------- |
| SVG | ![ svg ](assets/qr.svg) |
| PNG | ![ png ](assets/qr.png) |
| JPG | ![ jpg ](assets/qr.jpg) |
| PNG64 | [ sample ](assets/base64.html) |
| JPG64 | -- |
| Format | Sample |
| ------ | --------------------------------- |
| SVG | ![ svg ](assets/qr.svg) |
| PNG | ![ png ](assets/qr.png) |
| JPG | ![ jpg ](assets/qr.jpg) |
| PNG64 | [ sample ](assets/base64.html) |
| JPG64 | -- |
Error correction:
| L | M | Q | H |
| -------------------------- |--------------------------- | -------------------------- | -------------------------- |
| ![ l ](assets/qr_ec_l.jpg) | ![ m ](assets/qr_ec_m.jpg) | ![ q ](assets/qr_ec_q.jpg) | ![ h ](assets/qr_ec_h.jpg) |
"""

alias Qrusty.QR

@default_size 200
@default_ec :m

import Keyword, only: [get: 2, get: 3]

Expand All @@ -73,19 +86,46 @@ defmodule Qrusty do
]
| []

defmodule Error do
defexception message: "a error has occured"

@impl true
def exception(reason) when is_atom(reason), do: exception(Atom.to_string(reason))
def exception(reason), do: %__MODULE__{message: reason}
end

@doc """
Generate a QR code.
## Example
iex> Qrusty.qr("https://elixir-lang.org/", size: 100);
iex> Qrusty.qr("https://elixir-lang.org/", :svg, size: 100);
{:ok, %QR{}}
iex> Qrusty.qr("https://elixir-lang.org/", width: 100, height: 100);
iex> Qrusty.qr("https://elixir-lang.org/", :png, width: 100, height: 100);
{:ok, %QR{}}
"""
@spec qr(data, format, opts) :: {:ok, %QR{}} | {:error, any()}
def qr(data, format, opts \\ []) do
QR.new(data, format, width(opts), height(opts))
QR.new(data, format, width(opts), height(opts), error_correction(opts))
end

@doc """
Generate a QR code.
## Example
iex> Qrusty.qr!("https://elixir-lang.org/", :svg, size: 100);
"..."
Raises `Qrusty.Error` if the input is invalid
"""
@spec qr!(data, format, opts) :: binary()
def qr!(data, format, opts \\ []) do
QR.new(data, format, width(opts), height(opts), error_correction(opts))
|> case do
{:ok, %{encoded_data: qr}} -> qr
{:error, reason} -> raise Error, reason
end
end

defp width(opts) do
Expand All @@ -95,4 +135,6 @@ defmodule Qrusty do
defp height(opts) do
get(opts, :height) || get(opts, :size, @default_size)
end

defp error_correction(opts), do: get(opts, :ec, @default_ec)
end
40 changes: 24 additions & 16 deletions lib/qrusty/native.ex
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
defmodule Qrusty.Native.Options do
defstruct format: :svg,
width: 200,
height: 200,
error_correction: :m
end

defmodule Qrusty.Native do
@moduledoc """
Generates QR Codes by executing a Rust NIF.
Expand All @@ -15,29 +22,30 @@ defmodule Qrusty.Native do
force_build: System.get_env("QRUSTY_BUILD") in ["1", "true"],
version: version

@doc false
def generate(data, :svg, w, h), do: svg_nif(data, w, h)

def generate(data, :png, w, h), do: png_nif(data, w, h)

def generate(data, :png64, w, h), do: png64_nif(data, w, h)

def generate(data, f, w, h) when f in ~w(jpg jpeg)a, do: jpg_nif(data, w, h)

def generate(data, f, w, h) when f in ~w(jpg64 jpeg64)a, do: jpg64_nif(data, w, h)
alias Qrusty.Native.Options

@doc false
def svg_nif(_data, _w, _h), do: :erlang.nif_error(:nif_not_loaded)
def generate(data, :svg, w, h, ec) do
opts = %Options{format: :svg, width: w, height: h, error_correction: ec}
svg_nif(data, opts)
end

@doc false
def png_nif(_data, _w, _h), do: :erlang.nif_error(:nif_not_loaded)
def generate(data, f, w, h, ec) when f in ~w(png64 jpg64 jpeg64)a do
opts = %Options{format: f, width: w, height: h, error_correction: ec}
image_base64_nif(data, opts)
end

def generate(data, f, w, h, ec) when f in ~w(png jpg jpeg)a do
opts = %Options{format: f, width: w, height: h, error_correction: ec}
image_binary_nif(data, opts)
end

@doc false
def png64_nif(_data, _w, _h), do: :erlang.nif_error(:nif_not_loaded)
def svg_nif(_data, _opts), do: :erlang.nif_error(:nif_not_loaded)

@doc false
def jpg_nif(_data, _w, _h), do: :erlang.nif_error(:nif_not_loaded)
def image_binary_nif(_data, _opts), do: :erlang.nif_error(:nif_not_loaded)

@doc false
def jpg64_nif(_data, _width, _height), do: :erlang.nif_error(:nif_not_loaded)
def image_base64_nif(_data, _opts), do: :erlang.nif_error(:nif_not_loaded)
end
17 changes: 14 additions & 3 deletions lib/qrusty/qr.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,25 @@ defmodule Qrusty.QR do
@doc """
Validates input arguements and generates a QR if valid.
"""
@spec new(data :: binary(), format :: atom(), width :: integer(), height: integer()) ::
@spec new(
data :: binary(),
format :: atom(),
width :: integer(),
height :: integer(),
error_correction :: integer()
) ::
{:ok, %__MODULE__{}}
| {:error, :invalid_dimensions}
| {:error, :invalid_format}
| {:error, :invalid_data}
def new(data, format, width, height) do
| {:error, :invalid_error_correction}
def new(data, format, width, height, ec) do
with :ok <- validate_data(data),
:ok <- validate_format(format),
:ok <- validate_dimension(width),
:ok <- validate_dimension(height),
{:ok, encoded_data} <- Native.generate(data, format, width, height) do
:ok <- validate_error_correction(ec),
{:ok, encoded_data} <- Native.generate(data, format, width, height, ec) do
{:ok,
%__MODULE__{
data: data,
Expand All @@ -58,4 +66,7 @@ defmodule Qrusty.QR do

defp validate_data(d) when is_binary(d), do: :ok
defp validate_data(_d), do: {:error, :invalid_data}

defp validate_error_correction(ec) when ec in ~w(l m q h)a, do: :ok
defp validate_error_correction(_ec), do: {:error, :invalid_error_correction}
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Qrusty.MixProject do
use Mix.Project

@version "0.1.3"
@version "0.1.4"
@source_url "https://github.com/nbw/qrusty"

def project do
Expand Down
2 changes: 1 addition & 1 deletion native/qrusty/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion native/qrusty/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "qrusty"
version = "0.1.0"
version = "0.1.4"
edition = "2021"

[lib]
Expand Down
Loading

0 comments on commit 281f5e6

Please sign in to comment.