Skip to content

Commit

Permalink
Merge pull request #16 from ouven/master
Browse files Browse the repository at this point in the history
support versions up to 40 and ecc_l l/m/q/h
  • Loading branch information
nthock authored May 5, 2021
2 parents 559e965 + 6a9df35 commit 5293699
Show file tree
Hide file tree
Showing 9 changed files with 1,260 additions and 200 deletions.
9 changes: 5 additions & 4 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"],
line_length: 180
]
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ eqrcode-*.tar
.elixir_ls

/test/images/
/test/html/
/test/html/
.mix_tasks
33 changes: 18 additions & 15 deletions lib/eqrcode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ defmodule EQRCode do

alias EQRCode.{Encode, ReedSolomon, Matrix}

@type error_correction_level :: :l | :m | :q | :h

@doc """
Encode the binary.
"""
@spec encode(binary) :: Matrix.t()
def encode(bin) when byte_size(bin) <= 154 do
data =
Encode.encode(bin)
@spec encode(binary, error_correction_level()) :: Matrix.t()
def encode(bin, error_correction_level \\ :l)

def encode(bin, error_correction_level) when byte_size(bin) <= 2952 do
{version, error_correction_level, data} =
Encode.encode(bin, error_correction_level)
|> ReedSolomon.encode()

Encode.version(bin)
|> Matrix.new()
Matrix.new(version, error_correction_level)
|> Matrix.draw_finder_patterns()
|> Matrix.draw_seperators()
|> Matrix.draw_alignment_patterns()
Expand All @@ -39,23 +42,23 @@ defmodule EQRCode do
|> Matrix.draw_quite_zone()
end

def encode(bin) when is_nil(bin) do
def encode(bin, _error_correction_level) when is_nil(bin) do
raise(ArgumentError, message: "you must pass in some input")
end

def encode(_),
do: raise(ArgumentError, message: "your input is too long. keep it under 155 characters")
def encode(_, _),
do: raise(ArgumentError, message: "your input is too long. keep it under 2952 characters")

@doc """
Encode the binary with custom pattern bits. Only supports version 5.
"""
@spec encode(binary, bitstring) :: Matrix.t()
def encode(bin, bits) when byte_size(bin) <= 106 do
data =
Encode.encode(bin, bits)
@spec encode(binary, error_correction_level(), bitstring) :: Matrix.t()
def encode(bin, error_correction_level, bits) when byte_size(bin) <= 106 do
{version, error_correction_level, data} =
Encode.encode(bin, error_correction_level, bits)
|> ReedSolomon.encode()

Matrix.new(5)
Matrix.new(version, error_correction_level)
|> Matrix.draw_finder_patterns()
|> Matrix.draw_seperators()
|> Matrix.draw_alignment_patterns()
Expand All @@ -67,7 +70,7 @@ defmodule EQRCode do
|> Matrix.draw_quite_zone()
end

def encode(_, _), do: IO.puts("Binary too long.")
def encode(_, _, _), do: IO.puts("Binary too long.")

@doc """
```elixir
Expand Down
76 changes: 36 additions & 40 deletions lib/eqrcode/encode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,63 +3,57 @@ defmodule EQRCode.Encode do
Data encoding in Byte Mode.
"""

alias EQRCode.SpecTable
import Bitwise

@byte_mode 0b0100
@error_correction_level SpecTable.error_correction_level()

@pad <<236, 17>>
@capacity_l [0, 17, 32, 53, 78, 106, 134, 154]
@ecc_l %{
1 => 19,
2 => 34,
3 => 55,
4 => 80,
5 => 108,
6 => 136,
7 => 156
}
@mask0 <<0x99999999999999666666666666669966666666659999999996699533333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD333333333333332CCD332CCCCCCCCCCCCCCD33333333333333333332CCCCCCCCCD33333333::1072>>

@doc """
Encode the binary.
Example:
iex> QRCode.Encode.encode("hello world!")
{1, [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
iex> EQRCode.Encode.encode("hello world!", :l)
{1, :l, [0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1,
0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1,
1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0,
0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1,
1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0]}
"""
@spec encode(binary) :: {integer, [0 | 1]}
def encode(bin) do
version = version(bin)
@spec encode(binary, SpecTable.error_correction_level()) :: {SpecTable.version(), SpecTable.error_correction_level(), [0 | 1]}
def encode(bin, error_correction_level) when error_correction_level in @error_correction_level do
{:ok, version} = version(bin, error_correction_level)
cci_len = SpecTable.character_count_indicator_bits(version, error_correction_level)
mode = SpecTable.mode_indicator()

encoded =
[<<@byte_mode::4>>, <<byte_size(bin)>>, bin, <<0::4>>]
[<<mode::4>>, <<byte_size(bin)::size(cci_len)>>, bin, <<0::4>>]
|> Enum.flat_map(&bits/1)
|> pad_bytes(version)
|> pad_bytes(version, error_correction_level)

{version, encoded}
{version, error_correction_level, encoded}
end

@doc """
Encode the binary with custom pattern bits.
"""
@spec encode(binary, bitstring) :: {integer, [0 | 1]}
def encode(bin, bits) do
# Encode the binary with custom pattern bits.
@spec encode(binary, SpecTable.error_correction_level(), bitstring) :: {SpecTable.version(), SpecTable.error_correction_level(), [0 | 1]}
def encode(bin, error_correction_level, bits) when error_correction_level in @error_correction_level do
version = 5
n = byte_size(bin)
n1 = n + 2
n2 = @ecc_l[version] - n1
n2 = SpecTable.code_words_len(version, error_correction_level) - n1
cci_len = SpecTable.character_count_indicator_bits(version, error_correction_level)
mode = SpecTable.mode_indicator()
<<_::binary-size(n1), mask::binary-size(n2), _::binary>> = @mask0

encoded =
<<@byte_mode::4, n::8, bin::binary-size(n), 0::4, xor(bits, mask)::bits>>
<<mode::4, n::size(cci_len), bin::binary-size(n), 0::4, xor(bits, mask)::bits>>
|> bits()
|> pad_bytes(version)
|> pad_bytes(version, error_correction_level)

{version, encoded}
{version, error_correction_level, encoded}
end

defp xor(<<>>, _), do: <<>>
Expand All @@ -73,32 +67,34 @@ defmodule EQRCode.Encode do
Returns the lowest version for the given binary.
Example:
iex> QRCode.Encode.version("hello world!")
1
iex> EQRCode.Encode.version("hello world!", :l)
{:ok, 1}
"""
@spec version(binary) :: integer
def version(bin) do
len = byte_size(bin)
Enum.find_index(@capacity_l, &(&1 >= len))
@spec version(binary, SpecTable.error_correction_level()) :: {:error, :no_version_found} | {:ok, SpecTable.version()}
def version(bin, error_correction_level) do
byte_size(bin)
|> SpecTable.find_version(error_correction_level)
end

@doc """
Returns bits for any binary data.
Example:
iex> QRCode.Encode.bits(<<123, 4>>)
iex> EQRCode.Encode.bits(<<123, 4>>)
[0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]
"""
@spec bits(bitstring) :: [0 | 1]
def bits(bin) do
for <<b::1 <- bin>>, do: b
end

defp pad_bytes(list, version) do
n = @ecc_l[version] * 8 - length(list)
defp pad_bytes(list, version, error_correction_level) do
n = SpecTable.code_words_len(version, error_correction_level) * 8 - length(list)

Stream.cycle(bits(@pad))
|> Stream.take(n)
|> (&Enum.concat(list, &1)).()
Enum.concat(
list,
Stream.cycle(bits(@pad))
|> Stream.take(n)
)
end
end
32 changes: 12 additions & 20 deletions lib/eqrcode/galois_field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,6 @@ defmodule EQRCode.GaloisField do

import Bitwise

@integers %{}
@alphas %{}

Stream.iterate(1, fn e ->
n = e <<< 1
if n >= 256, do: n ^^^ 0b100011101, else: n
end)
|> Stream.take(256)
|> Stream.with_index()
|> Enum.each(fn {e, i} ->
Module.put_attribute(__MODULE__, :alphas, Map.put(@alphas, e, i))
Module.put_attribute(__MODULE__, :integers, Map.put(@integers, i, e))
end)

@doc """
Given alpha exponent returns integer.
Expand All @@ -25,9 +11,7 @@ defmodule EQRCode.GaloisField do
2
"""
@spec to_i(integer) :: integer
def to_i(alpha) do
@integers[alpha]
end
def to_i(alpha)

@doc """
Given integer returns alpha exponent.
Expand All @@ -37,7 +21,15 @@ defmodule EQRCode.GaloisField do
1
"""
@spec to_a(integer) :: integer
def to_a(integer) do
@alphas[integer]
end
def to_a(integer)

Stream.iterate({1, 0}, fn {e, i} ->
n = e <<< 1
{if(n >= 256, do: n ^^^ 0b100011101, else: n), i + 1}
end)
|> Enum.take(256)
|> Enum.map(fn {e, i} ->
def to_i(unquote(i)), do: unquote(e)
def to_a(unquote(e)), do: unquote(i)
end)
end
Loading

0 comments on commit 5293699

Please sign in to comment.