├── .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 | ![demo](priv/demo.png) 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 | --------------------------------------------------------------------------------