├── .gitignore
├── README.md
├── lib
├── qrcode.ex
└── qrcode
│ ├── encode.ex
│ ├── galois_field.ex
│ ├── mask.ex
│ ├── matrix.ex
│ ├── reed_solomon.ex
│ └── render.ex
├── mix.exs
├── priv
├── demo.png
├── qrcode.png
├── render.png
└── render2.png
└── test
├── qrcode_test.exs
└── test_helper.exs
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | .DS_Store
23 | *.swp
24 |
25 | /config
26 | .iex.exs
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # QRCode
2 |
3 | QR Code in Elixir.
4 |
5 | 
6 |
7 | Spec:
8 | - Version: 1 - 7
9 | - ECC level: L
10 | - Encoding mode: Byte
11 |
12 | References:
13 | - ISO/IEC 18004:2006(E)
14 | - http://www.thonky.com/qr-code-tutorial/
15 |
16 | Slide:
17 | - [Implementing QR Code](https://www.slideshare.net/BoshanSun/implementing-qrcode)
18 |
19 | ## Usage
20 |
21 | ```
22 | $ iex -S mix
23 | iex> QRCode.encode("https://www.google.com") |> QRCode.render()
24 | iex> QRCode.encode("你好,世界!") |> QRCode.render()
25 | iex> QRCode.encode("unicode support 😃") |> QRCode.render()
26 | ```
27 |
28 | ## Draw custom pattern
29 |
30 | 1. Start with the data you want to encode, along with all 0s pattern.
31 | ```
32 | iex> data = QRCode.encode("https://github.com/sunboshan/qrcode", <<0::600>>)
33 | ```
34 |
35 | 2. Choose to render QR Code in normal position or rotate 90 degree clockwise.
36 | ```
37 | iex> QRCode.render(data)
38 | iex> QRCode.render2(data)
39 | ```
40 |
41 |
42 | 3. Draw a custom pattern by changing bits in the second parameter.
43 | ```
44 | iex> QRCode.encode("https://github.com/sunboshan/qrcode", <<0b110110101010110101101101101010101101011010110110101010110110100101111101101011010110101011101010110101011010101011011101010101010001010100010101000101010101000101010001010101000001010001010100000000000111010101110111011111110101011101010111010111110101100000000000000000000111000110011001100110011111100111011011000111011001100000000000000000000110010001100100011111100101011001000110010011100101100000000000000000000101010101010101010101010101010101010101010101010101000000000001101111001101010011011101000101000101000101010100010100010100010101000101000101110101010101101010000000000000000000000000::600>>) |> QRCode.render2()
45 | ```
46 |
47 |
--------------------------------------------------------------------------------
/lib/qrcode.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode do
2 | @moduledoc """
3 | QR Code implementation in Elixir.
4 |
5 | Spec:
6 | - Version: 1 - 7
7 | - ECC level: L
8 | - Encoding mode: Byte
9 |
10 | References:
11 | - ISO/IEC 18004:2006(E)
12 | - http://www.thonky.com/qr-code-tutorial/
13 | """
14 |
15 | @doc """
16 | Encode the binary.
17 | """
18 | @spec encode(binary) :: QRCode.Matrix.t
19 | def encode(bin) when byte_size(bin) <= 154 do
20 | data = QRCode.Encode.encode(bin)
21 | |> QRCode.ReedSolomon.encode()
22 |
23 | QRCode.Encode.version(bin)
24 | |> QRCode.Matrix.new()
25 | |> QRCode.Matrix.draw_finder_patterns()
26 | |> QRCode.Matrix.draw_seperators()
27 | |> QRCode.Matrix.draw_alignment_patterns()
28 | |> QRCode.Matrix.draw_timing_patterns()
29 | |> QRCode.Matrix.draw_dark_module()
30 | |> QRCode.Matrix.draw_reserved_format_areas()
31 | |> QRCode.Matrix.draw_reserved_version_areas()
32 | |> QRCode.Matrix.draw_data_with_mask(data)
33 | |> QRCode.Matrix.draw_format_areas()
34 | |> QRCode.Matrix.draw_version_areas()
35 | |> QRCode.Matrix.draw_quite_zone()
36 | end
37 | def encode(_), do: IO.puts "Binary too long."
38 |
39 | @doc """
40 | Encode the binary with custom pattern bits. Only supports version 5.
41 | """
42 | @spec encode(binary, bitstring) :: QRCode.Matrix.t
43 | def encode(bin, bits) when byte_size(bin) <= 106 do
44 | data = QRCode.Encode.encode(bin, bits)
45 | |> QRCode.ReedSolomon.encode()
46 |
47 | QRCode.Matrix.new(5)
48 | |> QRCode.Matrix.draw_finder_patterns()
49 | |> QRCode.Matrix.draw_seperators()
50 | |> QRCode.Matrix.draw_alignment_patterns()
51 | |> QRCode.Matrix.draw_timing_patterns()
52 | |> QRCode.Matrix.draw_dark_module()
53 | |> QRCode.Matrix.draw_reserved_format_areas()
54 | |> QRCode.Matrix.draw_data_with_mask0(data)
55 | |> QRCode.Matrix.draw_format_areas()
56 | |> QRCode.Matrix.draw_quite_zone()
57 | end
58 | def encode(_, _), do: IO.puts "Binary too long."
59 |
60 | defdelegate render(matrix), to: QRCode.Render
61 | defdelegate render2(matrix), to: QRCode.Render
62 | end
63 |
--------------------------------------------------------------------------------
/lib/qrcode/encode.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode.Encode do
2 | @moduledoc """
3 | Data encoding in Byte Mode.
4 | """
5 |
6 | import Bitwise
7 |
8 | @byte_mode 0b0100
9 | @pad <<236, 17>>
10 | @capacity_l [0, 17, 32, 53, 78, 106, 134, 154]
11 | @ecc_l %{
12 | 1 => 19,
13 | 2 => 34,
14 | 3 => 55,
15 | 4 => 80,
16 | 5 => 108,
17 | 6 => 136,
18 | 7 => 156,
19 | }
20 | @mask0 <<0x99999999999999666666666666669966666666659999999996699533333333332ccd332ccccccccccccccd333333333333332ccd332ccccccccccccccd333333333333332ccd332ccccccccccccccd333333333333332ccd332ccccccccccccccd333333333333332ccd332ccccccccccccccd33333333333333333332cccccccccd33333333::1072>>
21 |
22 | @doc """
23 | Encode the binary.
24 |
25 | Example:
26 | iex> QRCode.Encode.encode("hello world!")
27 | {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,
28 | 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,
29 | 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,
30 | 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,
31 | 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,
32 | 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0]}
33 | """
34 | @spec encode(binary) :: {integer, [0 | 1]}
35 | def encode(bin) do
36 | version = version(bin)
37 | encoded = [<<@byte_mode::4>>, <>, bin, <<0::4>>]
38 | |> Enum.flat_map(&bits/1)
39 | |> pad_bytes(version)
40 | {version, encoded}
41 | end
42 |
43 | @doc """
44 | Encode the binary with custom pattern bits.
45 | """
46 | @spec encode(binary, bitstring) :: {integer, [0 | 1]}
47 | def encode(bin, bits) do
48 | version = 5
49 | n = byte_size(bin)
50 | n1 = n + 2
51 | n2 = @ecc_l[version] - n1
52 | <<_::binary-size(n1), mask::binary-size(n2), _::binary>> = @mask0
53 | encoded = <<@byte_mode::4, n::8, bin::binary-size(n), 0::4, xor(bits, mask)::bits>>
54 | |> bits()
55 | |> pad_bytes(version)
56 | {version, encoded}
57 | end
58 |
59 | defp xor(<<>>, _), do: <<>>
60 | defp xor(_, <<>>), do: <<>>
61 | defp xor(<>, <>) do
62 | <<(a ^^^ b)::1, xor(t1, t2)::bits>>
63 | end
64 |
65 | @doc """
66 | Returns the lowest version for the given binary.
67 |
68 | Example:
69 | iex> QRCode.Encode.version("hello world!")
70 | 1
71 | """
72 | @spec version(binary) :: integer
73 | def version(bin) do
74 | len = byte_size(bin)
75 | Enum.find_index(@capacity_l, &(&1 >= len))
76 | end
77 |
78 | @doc """
79 | Returns bits for any binary data.
80 |
81 | Example:
82 | iex> QRCode.Encode.bits(<<123, 4>>)
83 | [0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]
84 | """
85 | @spec bits(bitstring) :: [0 | 1]
86 | def bits(bin) do
87 | for <>, do: b
88 | end
89 |
90 | defp pad_bytes(list, version) do
91 | n = @ecc_l[version] * 8 - length(list)
92 | Stream.cycle(bits(@pad))
93 | |> Stream.take(n)
94 | |> (&Enum.concat(list, &1)).()
95 | end
96 | end
97 |
--------------------------------------------------------------------------------
/lib/qrcode/galois_field.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode.GaloisField do
2 | @moduledoc """
3 | Galios Field GF(256) functions.
4 | """
5 |
6 | import Bitwise
7 |
8 | @doc """
9 | Given alpha exponent returns integer.
10 |
11 | Example:
12 | iex> QRCode.GaloisField.to_i(1)
13 | 2
14 | """
15 | @spec to_i(integer) :: integer
16 | def to_i(alpha)
17 |
18 | @doc """
19 | Given integer returns alpha exponent.
20 |
21 | Example:
22 | iex> QRCode.GaloisField.to_a(2)
23 | 1
24 | """
25 | @spec to_a(integer) :: integer
26 | def to_a(integer)
27 |
28 | Stream.iterate(1, fn e ->
29 | n = e <<< 1
30 | if n >= 256, do: n ^^^ 0b100011101, else: n
31 | end)
32 | |> Stream.take(256)
33 | |> Stream.with_index()
34 | |> Enum.each(fn {e, i} ->
35 | def to_i(unquote(i)), do: unquote(e)
36 | def to_a(unquote(e)), do: unquote(i)
37 | end)
38 | end
39 |
--------------------------------------------------------------------------------
/lib/qrcode/mask.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode.Mask do
2 | @moduledoc """
3 | Data masking functions.
4 | """
5 |
6 | @doc """
7 | Get the total score for the masked matrix.
8 | """
9 | @spec score(QRCode.Matrix.matrix) :: integer
10 | def score(matrix) do
11 | rule1(matrix) + rule2(matrix) + rule3(matrix) + rule4(matrix)
12 | end
13 |
14 | @doc """
15 | Check for consecutive blocks.
16 | """
17 | @spec rule1(QRCode.Matrix.matrix) :: integer
18 | def rule1(matrix) do
19 | matrix = for e <- Tuple.to_list(matrix), do: Tuple.to_list(e)
20 | Stream.concat(matrix, transform(matrix))
21 | |> Enum.reduce(0, &(do_rule1(&1, {nil, 0}, 0) + &2))
22 | end
23 |
24 | defp do_rule1([], _, acc), do: acc
25 | defp do_rule1([h | t], {_, 0}, acc), do: do_rule1(t, {h, 1}, acc)
26 | defp do_rule1([h | t], {h, 4}, acc), do: do_rule1(t, {h, 5}, acc + 3)
27 | defp do_rule1([h | t], {h, 5}, acc), do: do_rule1(t, {h, 5}, acc + 1)
28 | defp do_rule1([h | t], {h, n}, acc), do: do_rule1(t, {h, n + 1}, acc)
29 | defp do_rule1([h | t], {_, _}, acc), do: do_rule1(t, {h, 1}, acc)
30 |
31 | defp transform(matrix) do
32 | for e <- Enum.zip(matrix), do: Tuple.to_list(e)
33 | end
34 |
35 | @doc """
36 | Check for 2x2 blocks.
37 | """
38 | @spec rule2(QRCode.Matrix.matrix) :: integer
39 | def rule2(matrix) do
40 | z = tuple_size(matrix) - 2
41 | for i <- 0..z, j <- 0..z do
42 | QRCode.Matrix.shape({i, j}, {2, 2})
43 | |> Enum.map(&get(matrix, &1))
44 | end
45 | |> Enum.reduce(0, &do_rule2/2)
46 | end
47 |
48 | defp do_rule2([1, 1, 1, 1], acc), do: acc + 3
49 | defp do_rule2([0, 0, 0, 0], acc), do: acc + 3
50 | defp do_rule2([_, _, _, _], acc), do: acc
51 |
52 | @doc """
53 | Check for special blocks.
54 | """
55 | @spec rule3(QRCode.Matrix.matrix) :: integer
56 | def rule3(matrix) do
57 | z = tuple_size(matrix)
58 | for i <- 0..(z - 1), j <- 0..(z - 11) do
59 | [{{i, j}, {11, 1}}, {{j, i}, {1, 11}}]
60 | |> Stream.map(fn {a, b} ->
61 | QRCode.Matrix.shape(a, b)
62 | |> Enum.map(&get(matrix, &1))
63 | end)
64 | |> Enum.map(&do_rule3/1)
65 | end
66 | |> List.flatten()
67 | |> Enum.sum()
68 | end
69 |
70 | defp do_rule3([1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0]), do: 40
71 | defp do_rule3([0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1]), do: 40
72 | defp do_rule3([_, _, _, _, _, _, _, _, _, _, _]), do: 0
73 |
74 | @doc """
75 | Check for module's proportion.
76 | """
77 | @spec rule4(QRCode.Matrix.matrix) :: integer
78 | def rule4(matrix) do
79 | m = tuple_size(matrix)
80 | black = Tuple.to_list(matrix)
81 | |> Enum.reduce(0, fn e, acc ->
82 | Tuple.to_list(e)
83 | |> Enum.reduce(acc, &do_rule4/2)
84 | end)
85 | div(abs(div(black * 100, m * m) - 50), 5) * 10
86 | end
87 |
88 | defp do_rule4(1, acc), do: acc + 1
89 | defp do_rule4(_, acc), do: acc
90 |
91 | defp get(matrix, {x, y}) do
92 | get_in(matrix, [Access.elem(x), Access.elem(y)])
93 | end
94 |
95 | @doc """
96 | The mask algorithm.
97 | """
98 | @spec mask(integer, QRCode.Matrix.coordinate) :: 0 | 1
99 | def mask(0b000, {x, y}) when rem(x + y, 2) == 0, do: 1
100 | def mask(0b000, {_, _}), do: 0
101 | def mask(0b001, {x, _}) when rem(x, 2) == 0, do: 1
102 | def mask(0b001, {_, _}), do: 0
103 | def mask(0b010, {_, y}) when rem(y, 3) == 0, do: 1
104 | def mask(0b010, {_, _}), do: 0
105 | def mask(0b011, {x, y}) when rem(x + y, 3) == 0, do: 1
106 | def mask(0b011, {_, _}), do: 0
107 | def mask(0b100, {x, y}) when rem(div(x, 2) + div(y, 3), 2) == 0, do: 1
108 | def mask(0b100, {_, _}), do: 0
109 | def mask(0b101, {x, y}) when rem(x * y, 2) + rem(x * y, 3) == 0, do: 1
110 | def mask(0b101, {_, _}), do: 0
111 | def mask(0b110, {x, y}) when rem(rem(x * y, 2) + rem(x * y, 3), 2) == 0, do: 1
112 | def mask(0b110, {_, _}), do: 0
113 | def mask(0b111, {x, y}) when rem(rem(x + y, 2) + rem(x * y, 3), 2) == 0, do: 1
114 | def mask(0b111, {_, _}), do: 0
115 | end
116 |
--------------------------------------------------------------------------------
/lib/qrcode/matrix.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode.Matrix do
2 | @moduledoc """
3 | QRCode matrix functions.
4 |
5 | Here's the axis for the matrix.
6 | ```
7 | (0,0) (0,7) (0,20)
8 | +---------------------> y
9 | |
10 | |
11 | (7,0)| (7,7)
12 | |
13 | |
14 | |
15 | |
16 | v
17 | x
18 | (20,0) (20,20)
19 | ```
20 | """
21 |
22 | import Bitwise
23 |
24 | defstruct [:version, :modules, :mask, :matrix]
25 |
26 | @type matrix :: term
27 | @type t :: %__MODULE__{version: integer, modules: integer, matrix: matrix}
28 | @type coordinate :: {integer, integer}
29 |
30 | @ecc_l 0b01
31 | @alignments %{
32 | 1 => [],
33 | 2 => [6, 18],
34 | 3 => [6, 22],
35 | 4 => [6, 26],
36 | 5 => [6, 30],
37 | 6 => [6, 34],
38 | 7 => [6, 22, 38],
39 | }
40 | @finder_pattern [
41 | 1, 1, 1, 1, 1, 1, 1,
42 | 1, 0, 0, 0, 0, 0, 1,
43 | 1, 0, 1, 1, 1, 0, 1,
44 | 1, 0, 1, 1, 1, 0, 1,
45 | 1, 0, 1, 1, 1, 0, 1,
46 | 1, 0, 0, 0, 0, 0, 1,
47 | 1, 1, 1, 1, 1, 1, 1,
48 | ]
49 | @alignment_pattern [
50 | 1, 1, 1, 1, 1,
51 | 1, 0, 0, 0, 1,
52 | 1, 0, 1, 0, 1,
53 | 1, 0, 0, 0, 1,
54 | 1, 1, 1, 1, 1,
55 | ]
56 |
57 | @doc """
58 | Initialize the matrix.
59 | """
60 | @spec new(integer) :: t
61 | def new(version) do
62 | modules = (version - 1) * 4 + 21
63 | matrix = Tuple.duplicate(nil, modules)
64 | |> Tuple.duplicate(modules)
65 | %__MODULE__{version: version, modules: modules, matrix: matrix}
66 | end
67 |
68 | @doc """
69 | Draw the finder patterns, three at a time.
70 | """
71 | @spec draw_finder_patterns(t) :: t
72 | def draw_finder_patterns(%__MODULE__{matrix: matrix, modules: modules} = m) do
73 | z = modules - 7
74 | matrix = [{0, 0}, {z, 0}, {0, z}]
75 | |> Stream.flat_map(&shape(&1, {7, 7}))
76 | |> Stream.zip(Stream.cycle(@finder_pattern))
77 | |> Enum.reduce(matrix, fn {coordinate, v}, acc ->
78 | update(acc, coordinate, v)
79 | end)
80 | %{m | matrix: matrix}
81 | end
82 |
83 | @doc """
84 | Draw the seperators.
85 | """
86 | @spec draw_seperators(t) :: t
87 | def draw_seperators(%__MODULE__{matrix: matrix, modules: modules} = m) do
88 | z = modules - 8
89 | matrix = [{{0, 7}, {1, 8}}, {{0, z}, {1, 8}}, {{7, z}, {8, 1}},
90 | {{7, 0}, {8, 1}}, {{z, 0}, {8, 1}}, {{z, 7}, {1, 8}}]
91 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end)
92 | |> Enum.reduce(matrix, &update(&2, &1, 0))
93 | %{m | matrix: matrix}
94 | end
95 |
96 | @doc """
97 | Draw the alignment patterns.
98 | """
99 | @spec draw_alignment_patterns(t) :: t
100 | def draw_alignment_patterns(%__MODULE__{matrix: matrix, version: version} = m) do
101 | matrix = (for x <- @alignments[version], y <- @alignments[version], do: {x, y})
102 | |> Stream.filter(&available?(matrix, &1))
103 | |> Stream.map(fn {x, y} -> {x - 2, y - 2} end)
104 | |> Stream.flat_map(&shape(&1, {5, 5}))
105 | |> Stream.zip(Stream.cycle(@alignment_pattern))
106 | |> Enum.reduce(matrix, fn {coordinate, v}, acc ->
107 | update(acc, coordinate, v)
108 | end)
109 | %{m | matrix: matrix}
110 | end
111 |
112 | @doc """
113 | Draw the timing patterns.
114 | """
115 | @spec draw_timing_patterns(t) :: t
116 | def draw_timing_patterns(%__MODULE__{matrix: matrix, modules: modules} = m) do
117 | z = modules - 13
118 | matrix = [{z, 1}, {1, z}]
119 | |> Stream.flat_map(&shape({6, 6}, &1))
120 | |> Stream.zip(Stream.cycle([1, 0]))
121 | |> Enum.reduce(matrix, fn {coordinate, v}, acc ->
122 | update(acc, coordinate, v)
123 | end)
124 | %{m | matrix: matrix}
125 | end
126 |
127 | @doc """
128 | Draw the dark module.
129 | """
130 | @spec draw_dark_module(t) :: t
131 | def draw_dark_module(%__MODULE__{matrix: matrix, modules: modules} = m) do
132 | matrix = update(matrix, {modules - 8, 8}, 1)
133 | %{m | matrix: matrix}
134 | end
135 |
136 | @doc """
137 | Draw the reserved format information areas.
138 | """
139 | @spec draw_reserved_format_areas(t) :: t
140 | def draw_reserved_format_areas(%__MODULE__{matrix: matrix, modules: modules} = m) do
141 | z = modules - 8
142 | matrix = [{{0, 8}, {1, 9}}, {{z, 8}, {1, 8}},
143 | {{8, 0}, {9, 1}}, {{8, z}, {8, 1}}]
144 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end)
145 | |> Enum.reduce(matrix, &update(&2, &1, :reserved))
146 | %{m | matrix: matrix}
147 | end
148 |
149 | @doc """
150 | Draw the reserved version information areas.
151 | """
152 | @spec draw_reserved_version_areas(t) :: t
153 | def draw_reserved_version_areas(%__MODULE__{version: version} = m) when version < 7, do: m
154 | def draw_reserved_version_areas(%__MODULE__{matrix: matrix, modules: modules} = m) do
155 | z = modules - 11
156 | matrix = [{{0, z}, {3, 6}}, {{z, 0}, {6, 3}}]
157 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end)
158 | |> Enum.reduce(matrix, &update(&2, &1, :reserved))
159 | %{m | matrix: matrix}
160 | end
161 |
162 | @doc """
163 | Draw the data bits with mask.
164 | """
165 | @spec draw_data_with_mask(t, binary) :: t
166 | def draw_data_with_mask(%__MODULE__{matrix: matrix, modules: modules} = m, data) do
167 | candidate = Stream.unfold(modules - 1, fn
168 | -1 -> nil
169 | 8 -> {8, 5}
170 | n -> {n, n - 2}
171 | end)
172 | |> Stream.zip(Stream.cycle([:up, :down]))
173 | |> Stream.flat_map(fn {z, path} -> path(path, {modules - 1, z}) end)
174 | |> Stream.filter(&available?(matrix, &1))
175 | |> Stream.zip(QRCode.Encode.bits(data))
176 |
177 | {mask, _, matrix} = Stream.map(0b000..0b111, fn mask ->
178 | matrix = Enum.reduce(candidate, matrix, fn {coordinate, v}, acc ->
179 | update(acc, coordinate, v ^^^ QRCode.Mask.mask(mask, coordinate))
180 | end)
181 | {mask, QRCode.Mask.score(matrix), matrix}
182 | end)
183 | |> Enum.min_by(&elem(&1, 1))
184 |
185 | %{m | matrix: matrix, mask: mask}
186 | end
187 |
188 | @doc """
189 | Draw the data bits with mask 0.
190 | """
191 | @spec draw_data_with_mask0(t, binary) :: t
192 | def draw_data_with_mask0(%__MODULE__{matrix: matrix, modules: modules} = m, data) do
193 | matrix = Stream.unfold(modules - 1, fn
194 | -1 -> nil
195 | 8 -> {8, 5}
196 | n -> {n, n - 2}
197 | end)
198 | |> Stream.zip(Stream.cycle([:up, :down]))
199 | |> Stream.flat_map(fn {z, path} -> path(path, {modules - 1, z}) end)
200 | |> Stream.filter(&available?(matrix, &1))
201 | |> Stream.zip(QRCode.Encode.bits(data))
202 | |> Enum.reduce(matrix, fn {coordinate, v}, acc ->
203 | update(acc, coordinate, v ^^^ QRCode.Mask.mask(0, coordinate))
204 | end)
205 | %{m | matrix: matrix, mask: 0}
206 | end
207 |
208 | defp path(:up, {x, y}), do: for i <- x..0, j <- y..(y - 1), do: {i, j}
209 | defp path(:down, {x, y}), do: for i <- 0..x, j <- y..(y - 1), do: {i, j}
210 |
211 | @doc """
212 | Fill the reserved format information areas.
213 | """
214 | @spec draw_format_areas(t) :: t
215 | def draw_format_areas(%__MODULE__{matrix: matrix, modules: modules, mask: mask} = m) do
216 | data = QRCode.ReedSolomon.bch_encode(<<@ecc_l::2, mask::3>>)
217 | matrix = [{{8, 0}, {9, 1}}, {{7, 8}, {1, -6}},
218 | {{modules - 1, 8}, {1, -6}}, {{8, modules - 8}, {8, 1}}]
219 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end)
220 | |> Stream.filter(&reserved?(matrix, &1))
221 | |> Stream.zip(Stream.cycle(data))
222 | |> Enum.reduce(matrix, fn {coordinate, v}, acc ->
223 | put(acc, coordinate, v)
224 | end)
225 | %{m | matrix: matrix}
226 | end
227 |
228 | @doc """
229 | Fill the reserved version information areas.
230 | """
231 | @spec draw_version_areas(t) :: t
232 | def draw_version_areas(%__MODULE__{version: version} = m) when version < 7, do: m
233 | def draw_version_areas(%__MODULE__{matrix: matrix, modules: modules} = m) do
234 | data = QRCode.Encode.bits(<<0b000111110010010100::18>>)
235 | z = modules - 9
236 | matrix = [{{z, 5}, {1, -1}}, {{z, 4}, {1, -1}}, {{z, 3}, {1, -1}},
237 | {{z, 2}, {1, -1}}, {{z, 1}, {1, -1}}, {{z, 0}, {1, -1}},
238 | {{5, z}, {-1, 1}}, {{4, z}, {-1, 1}}, {{3, z}, {-1, 1}},
239 | {{2, z}, {-1, 1}}, {{1, z}, {-1, 1}}, {{0, z}, {-1, 1}}]
240 | |> Stream.flat_map(fn {a, b} -> shape(a, b) end)
241 | |> Stream.filter(&reserved?(matrix, &1))
242 | |> Stream.zip(Stream.cycle(data))
243 | |> Enum.reduce(matrix, fn {coordinate, v}, acc ->
244 | put(acc, coordinate, v)
245 | end)
246 | %{m | matrix: matrix}
247 | end
248 |
249 | defp reserved?(matrix, {x, y}) do
250 | get_in(matrix, [Access.elem(x), Access.elem(y)]) == :reserved
251 | end
252 |
253 | defp put(matrix, {x, y}, value) do
254 | put_in(matrix, [Access.elem(x), Access.elem(y)], value)
255 | end
256 |
257 | @doc """
258 | Draw the quite zone.
259 | """
260 | @spec draw_quite_zone(t) :: t
261 | def draw_quite_zone(%__MODULE__{matrix: matrix, modules: modules} = m) do
262 | zone = Tuple.duplicate(0, modules + 4)
263 | matrix = Enum.reduce(0..(modules - 1), matrix, fn i, acc ->
264 | update_in(acc, [Access.elem(i)], fn row ->
265 | Tuple.insert_at(row, 0, 0)
266 | |> Tuple.insert_at(0, 0)
267 | |> Tuple.append(0)
268 | |> Tuple.append(0)
269 | end)
270 | end)
271 | |> Tuple.insert_at(0, zone)
272 | |> Tuple.insert_at(0, zone)
273 | |> Tuple.append(zone)
274 | |> Tuple.append(zone)
275 | %{m | matrix: matrix}
276 | end
277 |
278 | @doc """
279 | Given the starting point {x, y} and {width, height}
280 | returns the coordinates of the shape.
281 |
282 | Example:
283 | iex> QRCode.Matrix.shape({0, 0}, {3, 3})
284 | [{0, 0}, {0, 1}, {0, 2},
285 | {1, 0}, {1, 1}, {1, 2},
286 | {2, 0}, {2, 1}, {2, 2}]
287 | """
288 | @spec shape(coordinate, {integer, integer}) :: [coordinate]
289 | def shape({x, y}, {w, h}) do
290 | for i <- x..(x + h - 1), j <- y..(y + w - 1), do: {i, j}
291 | end
292 |
293 | defp update(matrix, {x, y}, value) do
294 | update_in(matrix, [Access.elem(x), Access.elem(y)], fn
295 | nil -> value
296 | val -> val
297 | end)
298 | end
299 |
300 | defp available?(matrix, {x, y}) do
301 | get_in(matrix, [Access.elem(x), Access.elem(y)]) == nil
302 | end
303 | end
304 |
--------------------------------------------------------------------------------
/lib/qrcode/reed_solomon.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode.ReedSolomon do
2 | @moduledoc """
3 | Reed-Solomon Error Correction Coding.
4 | """
5 |
6 | import Bitwise
7 |
8 | @rs_block %{
9 | # version => {error_code_len, data_code_len, remainder_len}
10 | 1 => {07, 019, 0},
11 | 2 => {10, 034, 7},
12 | 3 => {15, 055, 7},
13 | 4 => {20, 080, 7},
14 | 5 => {26, 108, 7},
15 | 6 => {18, 068, 7},
16 | 7 => {20, 078, 0},
17 | }
18 | @format_generator_polynomial 0b10100110111
19 | @format_mask 0b101010000010010
20 |
21 | @doc """
22 | Returns generator polynomials in alpha exponent for given error code length.
23 |
24 | Example:
25 | iex> QRCode.ReedSolomon.generator_polynomial(10)
26 | [0, 251, 67, 46, 61, 118, 70, 64, 94, 32, 45]
27 | """
28 | def generator_polynomial(error_code_len)
29 |
30 | Stream.iterate({[0, 0], 1}, fn {e, i} ->
31 | {rest, last} = Stream.map(e, &rem(&1 + i, 255))
32 | |> Enum.split(i)
33 | rest = Stream.zip(rest, tl(e))
34 | |> Enum.map(fn {x, y} ->
35 | QRCode.GaloisField.to_i(x) ^^^ QRCode.GaloisField.to_i(y)
36 | |> QRCode.GaloisField.to_a()
37 | end)
38 | {[0] ++ rest ++ last, i + 1}
39 | end)
40 | |> Stream.take(32)
41 | |> Enum.each(fn {e, i} ->
42 | def generator_polynomial(unquote(i)), do: unquote(e)
43 | end)
44 |
45 | @doc """
46 | Reed-Solomon encode.
47 |
48 | Example:
49 | iex> QRCode.ReedSolomon.encode(QRCode.Encode.encode("hello world!"))
50 | <<64, 198, 134, 86, 198, 198, 242, 7, 118, 247, 38, 198, 66, 16,
51 | 236, 17, 236, 17, 236, 45, 99, 25, 84, 35, 114, 46>>
52 | """
53 | @spec encode({integer, [0 | 1]}) :: [binary]
54 | def encode({version, message}) do
55 | {error_code_len, data_code_len, remainder_len} = @rs_block[version]
56 | gen_poly = generator_polynomial(error_code_len)
57 | data = Stream.chunk(message, 8)
58 | |> Stream.map(&String.to_integer(Enum.join(&1), 2))
59 | |> Stream.chunk(data_code_len)
60 | |> Stream.map(&{&1, polynomial_division(&1, gen_poly, data_code_len)})
61 | |> Enum.unzip()
62 | |> Tuple.to_list()
63 | |> Enum.flat_map(&interleave/1)
64 | |> :binary.list_to_bin()
65 | <>
66 | end
67 |
68 | defp interleave(list) do
69 | Enum.zip(list)
70 | |> Enum.flat_map(&Tuple.to_list/1)
71 | end
72 |
73 | @doc """
74 | Perform the polynomial division.
75 |
76 | Example:
77 | iex> QRCode.ReedSolomon.polynomial_division([64, 198, 134, 86, 198, 198, 242, 7, 118, 247, 38, 198, 66, 16, 236, 17, 236, 17, 236], [0, 87, 229, 146, 149, 238, 102, 21], 19)
78 | [45, 99, 25, 84, 35, 114, 46]
79 | """
80 | @spec polynomial_division(list, list, integer) :: list
81 | def polynomial_division(msg_poly, gen_poly, data_code_len) do
82 | Stream.iterate(msg_poly, &do_polynomial_division(&1, gen_poly))
83 | |> Enum.at(data_code_len)
84 | end
85 |
86 | defp do_polynomial_division([0 | t], _), do: t
87 | defp do_polynomial_division([h | _] = msg, gen_poly) do
88 | Stream.map(gen_poly, &rem(&1 + QRCode.GaloisField.to_a(h), 255))
89 | |> Enum.map(&QRCode.GaloisField.to_i/1)
90 | |> pad_zip(msg)
91 | |> Enum.map(fn {a, b} -> a ^^^ b end)
92 | |> tl()
93 | end
94 |
95 | defp pad_zip(left, right) do
96 | [short, long] = Enum.sort_by([left, right], &length/1)
97 | Stream.concat(short, Stream.cycle([0]))
98 | |> Stream.zip(long)
99 | end
100 |
101 | def bch_encode(data) do
102 | bch = do_bch_encode(QRCode.Encode.bits(<>))
103 | QRCode.Encode.bits(data) ++ bch
104 | |> Stream.zip(QRCode.Encode.bits(<<@format_mask::15>>))
105 | |> Enum.map(fn {a, b} -> a ^^^ b end)
106 | end
107 |
108 | defp do_bch_encode(list) when length(list) == 10, do: list
109 | defp do_bch_encode([0 | t]), do: do_bch_encode(t)
110 | defp do_bch_encode(list) do
111 | QRCode.Encode.bits(<<@format_generator_polynomial::11>>)
112 | |> Stream.concat(Stream.cycle([0]))
113 | |> Stream.zip(list)
114 | |> Enum.map(fn {a, b} -> a ^^^ b end)
115 | |> do_bch_encode()
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/lib/qrcode/render.ex:
--------------------------------------------------------------------------------
1 | defmodule QRCode.Render do
2 | @moduledoc """
3 | Render the QR Code matrix.
4 | """
5 |
6 | @doc """
7 | Render the QR Code to terminal.
8 | """
9 | @spec render(QRCode.Matrix.t) :: :ok
10 | def render(%QRCode.Matrix{matrix: matrix}) do
11 | Tuple.to_list(matrix)
12 | |> Stream.map(fn e ->
13 | Tuple.to_list(e)
14 | |> Enum.map(&do_render/1)
15 | end)
16 | |> Enum.intersperse("\n")
17 | |> IO.puts()
18 | end
19 |
20 | defp do_render(1), do: "\e[40m \e[0m"
21 | defp do_render(0), do: "\e[0;107m \e[0m"
22 | defp do_render(nil), do: "\e[0;106m \e[0m"
23 | defp do_render(:data), do: "\e[0;102m \e[0m"
24 | defp do_render(:reserved), do: "\e[0;104m \e[0m"
25 |
26 | @doc """
27 | Rotate the QR Code 90 degree clockwise and render to terminal.
28 | """
29 | @spec render2(QRCode.Matrix.t) :: :ok
30 | def render2(%QRCode.Matrix{matrix: matrix}) do
31 | (for e <- Tuple.to_list(matrix), do: Tuple.to_list(e))
32 | |> Enum.reverse()
33 | |> transform()
34 | |> Stream.map(fn e ->
35 | Enum.map(e, &do_render/1)
36 | end)
37 | |> Enum.intersperse("\n")
38 | |> IO.puts()
39 | end
40 |
41 | defp transform(matrix) do
42 | for e <- Enum.zip(matrix), do: Tuple.to_list(e)
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule QRCode.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :qrcode,
7 | version: "0.1.0",
8 | elixir: "~> 1.5",
9 | start_permanent: Mix.env == :prod,
10 | deps: deps()
11 | ]
12 | end
13 |
14 | # Run "mix help compile.app" to learn about applications.
15 | def application do
16 | [
17 | extra_applications: [:logger]
18 | ]
19 | end
20 |
21 | # Run "mix help deps" to learn about dependencies.
22 | defp deps do
23 | []
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/priv/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunboshan/qrcode/f6855c378b0d11e2aef6fb8c071280f367350643/priv/demo.png
--------------------------------------------------------------------------------
/priv/qrcode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunboshan/qrcode/f6855c378b0d11e2aef6fb8c071280f367350643/priv/qrcode.png
--------------------------------------------------------------------------------
/priv/render.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunboshan/qrcode/f6855c378b0d11e2aef6fb8c071280f367350643/priv/render.png
--------------------------------------------------------------------------------
/priv/render2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunboshan/qrcode/f6855c378b0d11e2aef6fb8c071280f367350643/priv/render2.png
--------------------------------------------------------------------------------
/test/qrcode_test.exs:
--------------------------------------------------------------------------------
1 | defmodule QRCodeTest do
2 | use ExUnit.Case
3 | doctest QRCode.Encode
4 | doctest QRCode.ReedSolomon
5 | doctest QRCode.GaloisField
6 | doctest QRCode.Matrix
7 | end
8 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------