Commit of qrcode encoder and supporting modules.
Includes demo module with basic png and crypto for a full-cyle demo with a mobile phone.
This commit is contained in:
commit
3939fb66e7
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
*.beam
|
||||||
|
*.dump
|
||||||
|
*.lnk
|
||||||
|
*.log
|
||||||
|
*.pdf
|
||||||
|
*.pem
|
||||||
|
*.png
|
||||||
|
test/*
|
||||||
|
work/*
|
2
Emakefile
Normal file
2
Emakefile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
% -*- mode:erlang -*-
|
||||||
|
{"src/*", [{i, "include"}, {outdir, "ebin"}, debug_info, strict_record_tests]}.
|
12
LICENSE
Normal file
12
LICENSE
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
29
README.markdown
Normal file
29
README.markdown
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
git remote add origin git@github.com:komone/qrcode.git
|
||||||
|
git push -u origin master
|
||||||
|
|
||||||
|
QR Code Encoder
|
||||||
|
===============
|
||||||
|
|
||||||
|
Reference used was ISO/IEC 18004, 1st Edition (2000)
|
||||||
|
|
||||||
|
This implementation is informed by my specific needs, i.e. to provide
|
||||||
|
two-factor authentication for mobile phones running Google Authenticator.
|
||||||
|
|
||||||
|
+ "Byte" mode only (don't need e.g. numeric mode or kanji mode).
|
||||||
|
+ Encode only (no detection/decode).
|
||||||
|
+ Basic supporting library functions provided (HOTP, PNG image functions) to allow full-cyle demo.
|
||||||
|
|
||||||
|
Demo
|
||||||
|
====
|
||||||
|
|
||||||
|
1. Download repo and compile with `erl -make`
|
||||||
|
2. Install Google Authenticator App on your mobile:
|
||||||
|
+ iPhone: http://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8
|
||||||
|
+ Android: https://market.android.com/details?id=com.google.android.apps.authenticator
|
||||||
|
3. Run demo: `qrcode_demo:run().`
|
||||||
|
4. Open the generated `qrcode.png` file
|
||||||
|
5. Scan the qrcode into the phone.
|
||||||
|
6. Ensure server clock is correct.
|
||||||
|
7. The value of `qrcode_demo:totp()` should show the same passcode as the phone.
|
||||||
|
|
||||||
|
NOTE: This documentation is rather basic as this was open-sourced by specific request!
|
9
ebin/qrcode.app
Normal file
9
ebin/qrcode.app
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{application, qrcode,
|
||||||
|
[{description, "QRCode Encoder"},
|
||||||
|
{vsn, "1.0.0"},
|
||||||
|
{modules, [qrcode, qrcode_matrix, qrcode_mask, reedsolomon, gf256, bits, base32]},
|
||||||
|
{mod, {qrcode, []}},
|
||||||
|
{registered, []},
|
||||||
|
{env, []},
|
||||||
|
{applications, [kernel, stdlib]}
|
||||||
|
]}.
|
79
src/base32.erl
Normal file
79
src/base32.erl
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(base32).
|
||||||
|
|
||||||
|
-export([encode/1, decode/1]).
|
||||||
|
|
||||||
|
-define(BASE32_ALPHABET, {
|
||||||
|
$A, $B, $C, $D, $E, $F, $G, $H,
|
||||||
|
$I, $J, $K, $L, $M, $N, $O, $P,
|
||||||
|
$Q, $R, $S, $T, $U, $V, $W, $X,
|
||||||
|
$Y, $Z, $2, $3, $4, $5, $6, $7
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% RFC 4648
|
||||||
|
|
||||||
|
%%
|
||||||
|
encode(Bin) when is_binary(Bin) ->
|
||||||
|
Split = 5 * (byte_size(Bin) div 5),
|
||||||
|
<<Main0:Split/binary, Rest/binary>> = Bin,
|
||||||
|
Main = << <<(b32e(C))>> || <<C:5>> <= Main0 >>,
|
||||||
|
encode0(Rest, Main).
|
||||||
|
|
||||||
|
encode0(<<>>, Acc) ->
|
||||||
|
Acc;
|
||||||
|
encode0(<<A:5, B:3>>, Acc) ->
|
||||||
|
<<Acc/binary, (b32e(A)), (b32e(B bsl 2)), "======">>;
|
||||||
|
encode0(<<A:5, B:5, C:5, D:1>>, Acc) ->
|
||||||
|
<<Acc/binary, (b32e(A)), (b32e(B)), (b32e(C)), (b32e(D bsl 4)), "====">>;
|
||||||
|
encode0(<<A:5, B:5, C:5, D:5, E:4>>, Acc) ->
|
||||||
|
<<Acc/binary, (b32e(A)), (b32e(B)), (b32e(C)), (b32e(D)), (b32e(E bsl 1)), "===">>;
|
||||||
|
encode0(<<A:5, B:5, C:5, D:5, E:5, F:5, G:2>>, Acc) ->
|
||||||
|
<<Acc/binary, (b32e(A)), (b32e(B)), (b32e(C)), (b32e(D)), (b32e(E)), (b32e(F)), (b32e(G bsl 3)), "=">>.
|
||||||
|
|
||||||
|
%%
|
||||||
|
decode(Bin) when is_binary(Bin) ->
|
||||||
|
Result = decode(Bin, <<>>),
|
||||||
|
true = is_binary(Result),
|
||||||
|
Result.
|
||||||
|
|
||||||
|
decode(<<X, "======">>, Acc) ->
|
||||||
|
Bits = decode0(X) bsr 2,
|
||||||
|
<<Acc/bits, Bits:3>>;
|
||||||
|
decode(<<X, "====">>, Acc) ->
|
||||||
|
Bits = decode0(X) bsr 4,
|
||||||
|
<<Acc/bits, Bits:1>>;
|
||||||
|
decode(<<X, "===">>, Acc) ->
|
||||||
|
Bits = decode0(X) bsr 1,
|
||||||
|
<<Acc/bits, Bits:4>>;
|
||||||
|
decode(<<X, "=">>, Acc) ->
|
||||||
|
Bits = decode0(X) bsr 3,
|
||||||
|
<<Acc/bits, Bits:2>>;
|
||||||
|
decode(<<A, Bin/binary>>, Acc) ->
|
||||||
|
Bits = decode0(A),
|
||||||
|
decode(Bin, <<Acc/bits, Bits:5>>);
|
||||||
|
decode(<<>>, Acc) ->
|
||||||
|
true = is_binary(Acc),
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
decode0(X) when X >= $A, X =< $Z ->
|
||||||
|
X - $A;
|
||||||
|
decode0(X) when X >= $2, X =< $7 ->
|
||||||
|
X - $2 + 26.
|
||||||
|
|
||||||
|
b32e(X) ->
|
||||||
|
element(X + 1, ?BASE32_ALPHABET).
|
||||||
|
|
||||||
|
|
69
src/bits.erl
Normal file
69
src/bits.erl
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(bits).
|
||||||
|
|
||||||
|
-export([reverse/1, duplicate/2, append/1, bitlist/1, bitstring/1, stringbits/1]).
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
reverse(Bin) ->
|
||||||
|
reverse(Bin, <<>>).
|
||||||
|
reverse(<<X:1, Bin/bits>>, Acc) ->
|
||||||
|
reverse(Bin, <<X:1, Acc/bits>>);
|
||||||
|
reverse(<<>>, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%%
|
||||||
|
duplicate(Bin, N) ->
|
||||||
|
duplicate(Bin, N, <<>>).
|
||||||
|
duplicate(Bin, N, Acc) when N > 0 ->
|
||||||
|
duplicate(Bin, N - 1, <<Acc/bits, Bin/bits>>);
|
||||||
|
duplicate(_, 0, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%%
|
||||||
|
append(List) ->
|
||||||
|
append(List, <<>>).
|
||||||
|
append([H|T], Acc) ->
|
||||||
|
append(T, <<Acc/bits, H/bits>>);
|
||||||
|
append([], Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%%
|
||||||
|
bitlist(Bin) ->
|
||||||
|
bitlist(Bin, []).
|
||||||
|
bitlist(<<X:1, Bin/bits>>, Acc) ->
|
||||||
|
bitlist(Bin, [X|Acc]);
|
||||||
|
bitlist(<<>>, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
%%
|
||||||
|
bitstring(Bin) ->
|
||||||
|
bitstring(Bin, <<>>).
|
||||||
|
bitstring(<<0:1, Bin/bits>>, Acc) ->
|
||||||
|
bitstring(Bin, <<Acc/binary, $0>>);
|
||||||
|
bitstring(<<1:1, Bin/bits>>, Acc) ->
|
||||||
|
bitstring(Bin, <<Acc/binary, $1>>);
|
||||||
|
bitstring(<<>>, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%%
|
||||||
|
stringbits(Bin) ->
|
||||||
|
stringbits(Bin, <<>>).
|
||||||
|
stringbits(<<$0, Bin/binary>>, Acc) ->
|
||||||
|
stringbits(Bin, <<Acc/bits, 0:1>>);
|
||||||
|
stringbits(<<$1, Bin/binary>>, Acc) ->
|
||||||
|
stringbits(Bin, <<Acc/bits, 1:1>>);
|
||||||
|
stringbits(<<>>, Acc) ->
|
||||||
|
Acc.
|
192
src/gf256.erl
Normal file
192
src/gf256.erl
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
%% NOTE: This module implements finite field arithmetic over the galois field
|
||||||
|
% GF(256) with a specified prime modulus.
|
||||||
|
|
||||||
|
-module(gf256).
|
||||||
|
|
||||||
|
-export([field/1, add/3, subtract/3, multiply/3]).
|
||||||
|
-export([exponent/2, log/2, inverse/2, value/3]).
|
||||||
|
-export([monomial_product/4, polynomial_product/3, divide/3]).
|
||||||
|
|
||||||
|
-record(gf256, {exponent, log}).
|
||||||
|
|
||||||
|
% UNUSED
|
||||||
|
%-record(gf256poly, {field, coefficients}).
|
||||||
|
% NOTE: Implementation and use are greatly simplified by expressing polynomials
|
||||||
|
% simply as lists of coefficient values, rather than explicit reification of
|
||||||
|
% polynomial "objects".
|
||||||
|
|
||||||
|
-define(RANGE, 255).
|
||||||
|
|
||||||
|
%%
|
||||||
|
field(PrimeModulus) ->
|
||||||
|
Exponent = exponent_table(1, PrimeModulus, []),
|
||||||
|
Log = log_table(Exponent, 1, [0]),
|
||||||
|
#gf256{exponent = Exponent, log = Log}.
|
||||||
|
%
|
||||||
|
exponent_table(X, Modulus, Acc) when length(Acc) =< ?RANGE ->
|
||||||
|
case X bsl 1 of
|
||||||
|
V when V > ?RANGE ->
|
||||||
|
X0 = V bxor Modulus;
|
||||||
|
V ->
|
||||||
|
X0 = V
|
||||||
|
end,
|
||||||
|
exponent_table(X0, Modulus, [X|Acc]);
|
||||||
|
exponent_table(_, _, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
log_table(E, Count, Acc) when Count =< ?RANGE ->
|
||||||
|
X = index_of(Count, 0, E),
|
||||||
|
log_table(E, Count + 1, [X|Acc]);
|
||||||
|
log_table(_, _, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
index_of(X, Count, [X|_]) ->
|
||||||
|
Count;
|
||||||
|
index_of(X, Count, [_|T]) ->
|
||||||
|
index_of(X, Count + 1, T).
|
||||||
|
|
||||||
|
%%
|
||||||
|
add(#gf256{}, A, B) when is_integer(A), is_integer(B) ->
|
||||||
|
A bxor B;
|
||||||
|
add(#gf256{}, [0], B) when is_list(B) ->
|
||||||
|
B;
|
||||||
|
add(#gf256{}, A, [0]) when is_list(A) ->
|
||||||
|
A;
|
||||||
|
add(F = #gf256{}, A, B) when is_list(A), is_list(B) ->
|
||||||
|
add(F, lists:reverse(A), lists:reverse(B), []).
|
||||||
|
|
||||||
|
add(F, [H|T], [H0|T0], Acc) ->
|
||||||
|
add(F, T, T0, [H bxor H0 | Acc]);
|
||||||
|
add(F, [H|T], [], Acc) ->
|
||||||
|
add(F, T, [], [H|Acc]);
|
||||||
|
add(F, [], [H|T], Acc) ->
|
||||||
|
add(F, [], T, [H|Acc]);
|
||||||
|
add(F, [], [], [0|Acc]) ->
|
||||||
|
add(F, [], [], Acc);
|
||||||
|
add(_, [], [], Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%% NOTE: Subtraction is the same as addition over a galois field.
|
||||||
|
subtract(F = #gf256{}, A, B) ->
|
||||||
|
add(F, A, B).
|
||||||
|
|
||||||
|
%%
|
||||||
|
multiply(#gf256{}, 0, _) ->
|
||||||
|
0;
|
||||||
|
multiply(#gf256{}, _, 0) ->
|
||||||
|
0;
|
||||||
|
multiply(#gf256{}, 1, B) ->
|
||||||
|
B;
|
||||||
|
multiply(#gf256{}, A, 1) ->
|
||||||
|
A;
|
||||||
|
multiply(#gf256{exponent = E, log = L}, A, B) ->
|
||||||
|
X = (lists:nth(A + 1, L) + lists:nth(B + 1, L)) rem ?RANGE,
|
||||||
|
lists:nth(X + 1, E).
|
||||||
|
|
||||||
|
%%
|
||||||
|
exponent(#gf256{exponent = E}, X) ->
|
||||||
|
lists:nth(X + 1, E).
|
||||||
|
|
||||||
|
%%
|
||||||
|
log(#gf256{log = L}, X) ->
|
||||||
|
lists:nth(X + 1, L).
|
||||||
|
|
||||||
|
%%
|
||||||
|
inverse(#gf256{exponent = E, log = L}, X) ->
|
||||||
|
lists:nth(256 - lists:nth(X + 1, L), E).
|
||||||
|
|
||||||
|
%%
|
||||||
|
value(#gf256{}, Poly, 0) ->
|
||||||
|
lists:last(Poly);
|
||||||
|
value(F = #gf256{}, Poly, 1) ->
|
||||||
|
lists:foldl(fun(X, Sum) -> gf256:add(F, X, Sum) end, 0, Poly);
|
||||||
|
value(F = #gf256{}, [H|T], X) ->
|
||||||
|
value(F, T, X, H).
|
||||||
|
%
|
||||||
|
value(F, [H|T], X, Acc) ->
|
||||||
|
Acc0 = multiply(F, X, Acc),
|
||||||
|
Acc1 = add(F, Acc0, H),
|
||||||
|
value(F, T, X, Acc1);
|
||||||
|
value(_, [], _, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%
|
||||||
|
monomial(#gf256{}, 0, Degree) when Degree >= 0 ->
|
||||||
|
[0];
|
||||||
|
monomial(#gf256{}, Coeff, Degree) when Degree >= 0 ->
|
||||||
|
[Coeff|lists:duplicate(Degree, 0)].
|
||||||
|
|
||||||
|
%%
|
||||||
|
monomial_product(#gf256{}, _, 0, _) ->
|
||||||
|
[0];
|
||||||
|
monomial_product(F, Poly, Coeff, Degree) ->
|
||||||
|
monomial_product(F, Poly, Coeff, Degree, []).
|
||||||
|
%
|
||||||
|
monomial_product(F, [H|T], C, D, Acc) ->
|
||||||
|
P = gf256:multiply(F, H, C),
|
||||||
|
monomial_product(F, T, C, D, [P|Acc]);
|
||||||
|
monomial_product(F, [], C, D, Acc) when D > 0 ->
|
||||||
|
monomial_product(F, [], C, D - 1, [0|Acc]);
|
||||||
|
monomial_product(_, [], _, 0, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
%%
|
||||||
|
polynomial_product(_, [0], _) ->
|
||||||
|
[0];
|
||||||
|
polynomial_product(_, _, [0]) ->
|
||||||
|
[0];
|
||||||
|
polynomial_product(F, P0, P1) ->
|
||||||
|
polynomial_product0(F, P0, P1, [], []).
|
||||||
|
%
|
||||||
|
polynomial_product0(F, [H|T], P1, P2, Acc) ->
|
||||||
|
[H0|T0] = polynomial_product1(F, H, P1, P2, []),
|
||||||
|
polynomial_product0(F, T, P1, T0, [H0|Acc]);
|
||||||
|
polynomial_product0(F, [], P1, [H|T], Acc) ->
|
||||||
|
polynomial_product0(F, [], P1, T, [H|Acc]);
|
||||||
|
polynomial_product0(_, [], _, [], Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
polynomial_product1(_, _, [], [], Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
polynomial_product1(F, X, [H|T], [], Acc) ->
|
||||||
|
Coeff = polynomial_product2(F, X, H, 0),
|
||||||
|
polynomial_product1(F, X, T, [], [Coeff|Acc]);
|
||||||
|
polynomial_product1(F, X, [H|T], [H0|T0], Acc) ->
|
||||||
|
Coeff = polynomial_product2(F, X, H, H0),
|
||||||
|
polynomial_product1(F, X, T, T0, [Coeff|Acc]).
|
||||||
|
|
||||||
|
polynomial_product2(F, X, H, H0) ->
|
||||||
|
Coeff = multiply(F, X, H),
|
||||||
|
add(F, H0, Coeff).
|
||||||
|
|
||||||
|
%%
|
||||||
|
divide(F = #gf256{}, A, B = [H|_]) when B =/= [0] ->
|
||||||
|
IDLT = inverse(F, H),
|
||||||
|
divide(F, IDLT, B, [0], A).
|
||||||
|
%
|
||||||
|
divide(F, IDLT, B, Q, R = [H|_]) when length(R) >= length(B), R =/= [0] ->
|
||||||
|
Diff = length(R) - length(B),
|
||||||
|
Scale = multiply(F, H, IDLT),
|
||||||
|
M = monomial(F, Scale, Diff),
|
||||||
|
Q0 = add(F, Q, M),
|
||||||
|
Coeffs = monomial_product(F, B, Scale, Diff),
|
||||||
|
R0 = add(F, R, Coeffs),
|
||||||
|
divide(F, IDLT, B, Q0, R0);
|
||||||
|
divide(_, _, _, Q, R) ->
|
||||||
|
{Q, R}.
|
||||||
|
|
||||||
|
|
194
src/qrcode.erl
Normal file
194
src/qrcode.erl
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(qrcode).
|
||||||
|
|
||||||
|
-include("qrcode.hrl").
|
||||||
|
-include("qrcode_params.hrl").
|
||||||
|
|
||||||
|
-export([encode/1, decode/1]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
decode(_Bin) ->
|
||||||
|
{error, not_implemented}.
|
||||||
|
|
||||||
|
%%
|
||||||
|
encode(Bin) ->
|
||||||
|
encode(Bin, 'M').
|
||||||
|
%
|
||||||
|
encode(Bin, ECC) when is_binary(Bin) ->
|
||||||
|
Params = choose_qr_params(Bin, ECC),
|
||||||
|
Content = encode_content(Params, Bin),
|
||||||
|
BlocksWithECC = generate_ecc_blocks(Params, Content),
|
||||||
|
Codewords = interleave_blocks(BlocksWithECC),
|
||||||
|
Matrix = qrcode_matrix:embed_data(Params, Codewords),
|
||||||
|
MaskedMatrices = qrcode_mask:generate(Params, Matrix),
|
||||||
|
Candidates = [qrcode_matrix:overlay_static(Params, M) || M <- MaskedMatrices],
|
||||||
|
{MaskType, SelectedMatrix} = qrcode_mask:select(Candidates),
|
||||||
|
Params0 = Params#qr_params{mask = MaskType},
|
||||||
|
FMT = format_info_bits(Params0),
|
||||||
|
VSN = version_info_bits(Params0),
|
||||||
|
#qr_params{version = Version, dimension = Dim, ec_level = _ECC} = Params0,
|
||||||
|
QRCode = qrcode_matrix:finalize(Dim, FMT, VSN, ?QUIET_ZONE, SelectedMatrix),
|
||||||
|
%% NOTE: Added "API" record
|
||||||
|
#qrcode{version = Version, ecc = ECC, dimension = Dim + ?QUIET_ZONE * 2, data = QRCode}.
|
||||||
|
|
||||||
|
%%
|
||||||
|
choose_qr_params(Bin, ECLevel) ->
|
||||||
|
Mode = choose_encoding(Bin),
|
||||||
|
{Mode, Version, ECCBlockDefs, Remainder} = choose_version(Mode, ECLevel, byte_size(Bin)),
|
||||||
|
AlignmentCoords = alignment_patterns(Version),
|
||||||
|
Dim = qrcode_matrix:dimension(Version),
|
||||||
|
#qr_params{mode = Mode, version = Version, dimension = Dim, ec_level = ECLevel,
|
||||||
|
block_defs = ECCBlockDefs, align_coords = AlignmentCoords, remainder = Remainder, data = Bin}.
|
||||||
|
|
||||||
|
%% NOTE: byte mode only (others removed)
|
||||||
|
choose_encoding(_Bin) ->
|
||||||
|
byte.
|
||||||
|
|
||||||
|
%%
|
||||||
|
choose_version(Type, ECC, Length) ->
|
||||||
|
choose_version(Type, ECC, Length, ?TABLES).
|
||||||
|
%
|
||||||
|
choose_version(byte, ECC, Length, [{{ECC, Version}, {_, _, Capacity, _}, ECCBlocks, Remainder}|_])
|
||||||
|
when Capacity >= Length ->
|
||||||
|
{byte, Version, ECCBlocks, Remainder};
|
||||||
|
choose_version(Type, ECC, Length, [_|T]) ->
|
||||||
|
choose_version(Type, ECC, Length, T).
|
||||||
|
|
||||||
|
%%
|
||||||
|
encode_content(#qr_params{mode = Mode, version = Version}, Bin) ->
|
||||||
|
encode_content(Mode, Version, Bin).
|
||||||
|
%
|
||||||
|
encode_content(byte, Version, Bin) ->
|
||||||
|
encode_bytes(Version, Bin).
|
||||||
|
|
||||||
|
%%
|
||||||
|
generate_ecc_blocks(#qr_params{block_defs = ECCBlockDefs}, Bin) ->
|
||||||
|
generate_ecc(Bin, ECCBlockDefs, []).
|
||||||
|
%
|
||||||
|
generate_ecc(Bin, [{C, L, D}|T], Acc) ->
|
||||||
|
{Result, Bin0} = generate_ecc0(Bin, C, L, D, []),
|
||||||
|
generate_ecc(Bin0, T, [Result|Acc]);
|
||||||
|
generate_ecc(<<>>, [], Acc) ->
|
||||||
|
lists:flatten(lists:reverse(Acc)).
|
||||||
|
%
|
||||||
|
generate_ecc0(Bin, Count, TotalLength, BlockLength, Acc) when byte_size(Bin) >= BlockLength, Count > 0 ->
|
||||||
|
<<Block:BlockLength/binary, Bin0/binary>> = Bin,
|
||||||
|
EC = qrcode_reedsolomon:encode(Block, TotalLength - BlockLength),
|
||||||
|
generate_ecc0(Bin0, Count, TotalLength, BlockLength, [{Block, EC}|Acc]);
|
||||||
|
generate_ecc0(Bin, Count, TotalLength, BlockLength, Acc) when Count > 0 ->
|
||||||
|
Block = pad_block(Bin, BlockLength),
|
||||||
|
EC = qrcode_reedsolomon:encode(Block, TotalLength - BlockLength),
|
||||||
|
{lists:reverse([{Block, EC}|Acc]), <<>>};
|
||||||
|
generate_ecc0(Bin, 0, _, _, Acc) ->
|
||||||
|
{lists:reverse(Acc), Bin}.
|
||||||
|
|
||||||
|
%%
|
||||||
|
interleave_blocks(Blocks) ->
|
||||||
|
Data = interleave_data(Blocks, <<>>),
|
||||||
|
interleave_ecc(Blocks, Data).
|
||||||
|
|
||||||
|
interleave_data(Blocks, Bin) ->
|
||||||
|
Data = [X || {X, _} <- Blocks],
|
||||||
|
interleave_blocks(Data, [], Bin).
|
||||||
|
|
||||||
|
interleave_ecc(Blocks, Bin) ->
|
||||||
|
Data = [X || {_, X} <- Blocks],
|
||||||
|
interleave_blocks(Data, [], Bin).
|
||||||
|
|
||||||
|
interleave_blocks([], [], Bin) ->
|
||||||
|
Bin;
|
||||||
|
interleave_blocks([], Acc, Bin) ->
|
||||||
|
Acc0 = [X || X <- Acc, X =/= <<>>],
|
||||||
|
interleave_blocks(lists:reverse(Acc0), [], Bin);
|
||||||
|
interleave_blocks([<<X, Data/binary>>|T], Acc, Bin) ->
|
||||||
|
interleave_blocks(T, [Data|Acc], <<Bin/binary, X>>).
|
||||||
|
|
||||||
|
%
|
||||||
|
encode_bytes(Version, Bin) when is_binary(Bin) ->
|
||||||
|
Size = size(Bin),
|
||||||
|
CharacterCountBitSize = cci(?BYTE_MODE, Version),
|
||||||
|
<<?BYTE_MODE:4, Size:CharacterCountBitSize, Bin/binary, 0:4>>.
|
||||||
|
%
|
||||||
|
pad_block(Bin, BlockLength) ->
|
||||||
|
PadCount = BlockLength - byte_size(Bin),
|
||||||
|
Pad = binary:copy(<<?DATA_PAD_0, ?DATA_PAD_1>>, PadCount bsr 1),
|
||||||
|
case PadCount band 1 of
|
||||||
|
0 ->
|
||||||
|
Padding = Pad;
|
||||||
|
1 ->
|
||||||
|
Padding = <<Pad/binary, ?DATA_PAD_0>>
|
||||||
|
end,
|
||||||
|
<<Bin/binary, Padding/binary>>.
|
||||||
|
|
||||||
|
%% Table 25. Error correction level indicators
|
||||||
|
ecc('L') -> 1;
|
||||||
|
ecc('M') -> 0;
|
||||||
|
ecc('Q') -> 3;
|
||||||
|
ecc('H') -> 2.
|
||||||
|
|
||||||
|
% Table 5. Charset encoder
|
||||||
|
% NOTE: removed
|
||||||
|
|
||||||
|
%%
|
||||||
|
alignment_patterns(Version) ->
|
||||||
|
D = qrcode_matrix:dimension(Version),
|
||||||
|
L = element(Version, ?ALIGNMENT_COORDINATES),
|
||||||
|
L0 = [{X, Y} || X <- L, Y <- L],
|
||||||
|
L1 = [{X, Y} || {X, Y} <- L0, is_finder_region(D, X, Y) =:= false],
|
||||||
|
% Change the natural sort order so that rows have greater weight than columns
|
||||||
|
F = fun
|
||||||
|
({_, Y}, {_, Y0}) when Y < Y0 ->
|
||||||
|
true;
|
||||||
|
({X, Y}, {X0, Y0}) when Y =:= Y0 andalso X =< X0 ->
|
||||||
|
true;
|
||||||
|
(_, _) ->
|
||||||
|
false
|
||||||
|
end,
|
||||||
|
lists:sort(F, L1).
|
||||||
|
%
|
||||||
|
is_finder_region(D, X, Y)
|
||||||
|
when (X =< 8 andalso Y =< 8)
|
||||||
|
orelse (X =< 8 andalso Y >= D - 8)
|
||||||
|
orelse (X >= D - 8 andalso Y =< 8) ->
|
||||||
|
true;
|
||||||
|
is_finder_region(_, _, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
%% Table 3. Number of bits in Character Count Indicator
|
||||||
|
cci(Mode, Version) when Version >= 1 andalso Version =< 40->
|
||||||
|
{Mode, CC} = lists:keyfind(Mode, 1, ?CCI_BITSIZE),
|
||||||
|
cci0(CC, Version).
|
||||||
|
%
|
||||||
|
cci0([X, _, _], Version) when Version =< 9 ->
|
||||||
|
X;
|
||||||
|
cci0([_, X, _], Version) when Version =< 26 ->
|
||||||
|
X;
|
||||||
|
cci0([_, _, X], _) ->
|
||||||
|
X.
|
||||||
|
|
||||||
|
version_info_bits(#qr_params{version = Version}) when Version < 7 ->
|
||||||
|
<<>>;
|
||||||
|
version_info_bits(#qr_params{version = Version}) when Version =< 40 ->
|
||||||
|
BCH = qrcode_reedsolomon:bch_code(Version, ?VERSION_INFO_POLY),
|
||||||
|
<<Version:6, BCH:12>>.
|
||||||
|
|
||||||
|
format_info_bits(#qr_params{ec_level = ECLevel, mask = MaskType}) ->
|
||||||
|
Info = (ecc(ECLevel) bsl 3) bor MaskType,
|
||||||
|
BCH = qrcode_reedsolomon:bch_code(Info, ?FORMAT_INFO_POLY),
|
||||||
|
InfoWithEC = (Info bsl 10) bor BCH,
|
||||||
|
Value = InfoWithEC bxor ?FORMAT_INFO_MASK,
|
||||||
|
<<Value:15>>.
|
||||||
|
|
18
src/qrcode.hrl
Normal file
18
src/qrcode.hrl
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
%
|
||||||
|
% qrcode.hrl
|
||||||
|
|
||||||
|
%% API Record
|
||||||
|
-record(qrcode, {version, ecc, dimension, data}).
|
136
src/qrcode_demo.erl
Normal file
136
src/qrcode_demo.erl
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(qrcode_demo).
|
||||||
|
|
||||||
|
%% Shows how to achieve HOTP/SHA1 with a mobile phone using Google Authenticator.
|
||||||
|
%%
|
||||||
|
%% This module is a rag-bag of supporting functions, many of which are simplified
|
||||||
|
%% extracts from the core libs (?_common, ?_crypto, ?_math, ?_image). This is to
|
||||||
|
%% allow a full-cycle demo without requiring open-sourcing of the entire platform.
|
||||||
|
%%
|
||||||
|
%% @ref QR Code: ISO/IEC 18004 (2000, 1st Edition)
|
||||||
|
|
||||||
|
%% Google Authenticator Phone App
|
||||||
|
%% iPhone: <http://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8>
|
||||||
|
%% Android: <https://market.android.com/details?id=com.google.android.apps.authenticator>
|
||||||
|
|
||||||
|
%% Google Authenticator URL Specification
|
||||||
|
% @ref <http://code.google.com/p/google-authenticator/wiki/KeyUriFormat>
|
||||||
|
% otpauth://TYPE/LABEL?PARAMETERS
|
||||||
|
% TYPE: hotp | totp
|
||||||
|
% LABEL: string() (usually email address)
|
||||||
|
% PARAMETERS:
|
||||||
|
% digits = 6 | 8 (default 6)
|
||||||
|
% counter = integer() (hotp only, default 0?)
|
||||||
|
% period = integer() (in seconds, totp only, default 30)
|
||||||
|
% secret = binary() base32 encoded
|
||||||
|
% algorithm = MD5 | SHA1 | SHA256 | SHA512 (default SHA1)
|
||||||
|
|
||||||
|
|
||||||
|
-include("qrcode.hrl").
|
||||||
|
|
||||||
|
-compile(export_all).
|
||||||
|
|
||||||
|
-define(TTY(Term), io:format(user, "[~p] ~p~n", [?MODULE, Term])).
|
||||||
|
|
||||||
|
run() ->
|
||||||
|
Passcode = crypto:sha(<<"password">>),
|
||||||
|
run(<<"demo@mydomain.com">>, Passcode, 60).
|
||||||
|
|
||||||
|
run(Domain, Passcode, Seconds) ->
|
||||||
|
PasscodeBase32 = base32:encode(Passcode),
|
||||||
|
Period = list_to_binary(integer_to_list(Seconds)),
|
||||||
|
Token = <<"otpauth://totp/", Domain/binary, "?period=", Period/binary, "&secret=", PasscodeBase32/binary>>,
|
||||||
|
?TTY({token, Token}),
|
||||||
|
QRCode = qrcode:encode(Token),
|
||||||
|
Image = simple_png_encode(QRCode),
|
||||||
|
Filename = "qrcode.png",
|
||||||
|
ok = file:write_file(Filename, Image),
|
||||||
|
?TTY({image, filename:absname(Filename)}),
|
||||||
|
QRCode.
|
||||||
|
|
||||||
|
%% Very simple PNG encoder for demo purposes
|
||||||
|
simple_png_encode(#qrcode{dimension = Dim, data = Data}) ->
|
||||||
|
MAGIC = <<137, 80, 78, 71, 13, 10, 26, 10>>,
|
||||||
|
Size = Dim * 8,
|
||||||
|
IHDR = png_chunk(<<"IHDR">>, <<Size:32, Size:32, 8:8, 2:8, 0:24>>),
|
||||||
|
PixelData = get_pixel_data(Dim, Data),
|
||||||
|
IDAT = png_chunk(<<"IDAT">>, PixelData),
|
||||||
|
IEND = png_chunk(<<"IEND">>, <<>>),
|
||||||
|
<<MAGIC/binary, IHDR/binary, IDAT/binary, IEND/binary>>.
|
||||||
|
|
||||||
|
png_chunk(Type, Bin) ->
|
||||||
|
Length = byte_size(Bin),
|
||||||
|
CRC = erlang:crc32(<<Type/binary, Bin/binary>>),
|
||||||
|
<<Length:32, Type/binary, Bin/binary, CRC:32>>.
|
||||||
|
|
||||||
|
get_pixel_data(Dim, Data) ->
|
||||||
|
Pixels = get_pixels(Data, 0, Dim, <<>>),
|
||||||
|
zlib:compress(Pixels).
|
||||||
|
|
||||||
|
get_pixels(<<>>, Dim, Dim, Acc) ->
|
||||||
|
Acc;
|
||||||
|
get_pixels(Bin, Count, Dim, Acc) ->
|
||||||
|
<<RowBits:Dim/bits, Bits/bits>> = Bin,
|
||||||
|
Row = get_pixels0(RowBits, <<0>>), % row filter byte
|
||||||
|
FullRow = binary:copy(Row, 8),
|
||||||
|
get_pixels(Bits, Count + 1, Dim, <<Acc/binary, FullRow/binary>>).
|
||||||
|
|
||||||
|
get_pixels0(<<1:1, Bits/bits>>, Acc) ->
|
||||||
|
Black = binary:copy(<<0>>, 24),
|
||||||
|
get_pixels0(Bits, <<Acc/binary, Black/binary>>);
|
||||||
|
get_pixels0(<<0:1, Bits/bits>>, Acc) ->
|
||||||
|
White = binary:copy(<<255>>, 24),
|
||||||
|
get_pixels0(Bits, <<Acc/binary, White/binary>>);
|
||||||
|
get_pixels0(<<>>, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%%
|
||||||
|
totp() ->
|
||||||
|
Key = crypto:sha(<<"password">>),
|
||||||
|
totp(Key, 60).
|
||||||
|
totp(Key, Period) ->
|
||||||
|
T = unow() div Period,
|
||||||
|
{hotp(Key, T - 1), hotp(Key, T), hotp(Key, T + 1)}.
|
||||||
|
%% RFC-4226 "HOTP: An HMAC-Based One-Time Password Algorithm"
|
||||||
|
%% @ref <http://tools.ietf.org/html/rfc4226>
|
||||||
|
hotp(Key, Count) when is_binary(Key), is_integer(Count) ->
|
||||||
|
HS = crypto:sha_mac(Key, <<Count:64>>),
|
||||||
|
<<_:19/binary, _:4, Offset:4>> = HS,
|
||||||
|
<<_:Offset/binary, _:1, P:31, _/binary>> = HS,
|
||||||
|
HOTP = integer_to_list(P rem 1000000),
|
||||||
|
Pad = lists:duplicate(6 - length(HOTP), $0),
|
||||||
|
list_to_binary([Pad, HOTP]).
|
||||||
|
|
||||||
|
|
||||||
|
-define(UNIX_TIME_ZERO, 62167219200).
|
||||||
|
|
||||||
|
unow() ->
|
||||||
|
calendar:datetime_to_gregorian_seconds(calendar:universal_time()) - ?UNIX_TIME_ZERO.
|
||||||
|
|
||||||
|
hex(Bin) when is_binary(Bin) ->
|
||||||
|
hex(Bin, <<>>).
|
||||||
|
|
||||||
|
hex(<<A:4, B:4, Rest/binary>>, Acc) ->
|
||||||
|
U = hex_digit(A),
|
||||||
|
L = hex_digit(B),
|
||||||
|
hex(Rest, <<Acc/binary, U, L>>);
|
||||||
|
hex(<<>>, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
hex_digit(D) when D >= 0, D =< 9 ->
|
||||||
|
$0 + D;
|
||||||
|
hex_digit(D) when D >= 10, D =< 16 ->
|
||||||
|
$a + D - 10.
|
267
src/qrcode_mask.erl
Normal file
267
src/qrcode_mask.erl
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(qrcode_mask).
|
||||||
|
|
||||||
|
-include("qrcode_params.hrl").
|
||||||
|
|
||||||
|
-export([generate/2, select/1]).
|
||||||
|
|
||||||
|
-define(PENALTY_RULE_1, 3).
|
||||||
|
-define(PENALTY_RULE_2, 3).
|
||||||
|
-define(PENALTY_RULE_3, 40).
|
||||||
|
-define(PENALTY_RULE_4, 10).
|
||||||
|
|
||||||
|
%% Generates all eight masked versions of the bit matrix
|
||||||
|
generate(#qr_params{dimension = Dim}, Matrix) ->
|
||||||
|
Sequence = lists:seq(0, 7),
|
||||||
|
Functions = [mask(X) || X <- Sequence],
|
||||||
|
Masks = [generate_mask(Dim, MF) || MF <- Functions],
|
||||||
|
[apply_mask(Matrix, Mask, []) || Mask <- Masks].
|
||||||
|
|
||||||
|
%% Selects the lowest penalty candidate from a list of bit matrices
|
||||||
|
select([H|T]) ->
|
||||||
|
Score = score_candidate(H),
|
||||||
|
select_candidate(T, 0, 0, Score, H).
|
||||||
|
|
||||||
|
%% Internal
|
||||||
|
|
||||||
|
%
|
||||||
|
generate_mask(Max, MF) ->
|
||||||
|
Sequence = lists:seq(0, Max - 1),
|
||||||
|
[generate_mask(Sequence, Y, MF) || Y <- Sequence].
|
||||||
|
generate_mask(Sequence, Y, MF) ->
|
||||||
|
[case MF(X, Y) of true -> 1; false -> 0 end || X <- Sequence].
|
||||||
|
|
||||||
|
apply_mask([H|T], [H0|T0], Acc) ->
|
||||||
|
Row = apply_mask0(H, H0, []),
|
||||||
|
apply_mask(T, T0, [Row|Acc]);
|
||||||
|
apply_mask([], [], Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
apply_mask0([H|T], [H0|T0], Acc) when is_integer(H) ->
|
||||||
|
apply_mask0(T, T0, [H bxor H0|Acc]);
|
||||||
|
apply_mask0([H|T], [_|T0], Acc) ->
|
||||||
|
apply_mask0(T, T0, [H|Acc]);
|
||||||
|
apply_mask0([], [], Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
% (i + j) mod 2 = 0
|
||||||
|
mask(0) ->
|
||||||
|
fun(X, Y) -> (X + Y) rem 2 =:= 0 end;
|
||||||
|
% i mod 2 = 0
|
||||||
|
mask(1) ->
|
||||||
|
fun(_X, Y) -> Y rem 2 =:= 0 end;
|
||||||
|
% j mod 3 = 0
|
||||||
|
mask(2) ->
|
||||||
|
fun(X, _Y) -> X rem 3 =:= 0 end;
|
||||||
|
% (i + j) mod 3 = 0
|
||||||
|
mask(3) ->
|
||||||
|
fun(X, Y) -> (X + Y) rem 3 =:= 0 end;
|
||||||
|
% ((i div 2) + (j div 3)) mod 2 = 0
|
||||||
|
mask(4) ->
|
||||||
|
fun(X, Y) -> (X div 3 + Y div 2) rem 2 =:= 0 end;
|
||||||
|
%101 (i * j) mod 2 + (i *j) mod 3 = 0
|
||||||
|
mask(5) ->
|
||||||
|
fun(X, Y) -> Sum = X * Y, Sum rem 2 + Sum rem 3 =:= 0 end;
|
||||||
|
% ((i * j) mod 2 + (i* j) mod 3) mod 2 = 0
|
||||||
|
mask(6) ->
|
||||||
|
fun(X, Y) -> Sum = X * Y, (Sum rem 2 + Sum rem 3) rem 2 =:= 0 end;
|
||||||
|
%((i * j) mod 3 + (i + j) mod 2) mod 2 = 0
|
||||||
|
mask(7) ->
|
||||||
|
fun(X, Y) -> ((X * Y rem 3) + ((X + Y) rem 2)) rem 2 =:= 0 end.
|
||||||
|
|
||||||
|
select_candidate([H|T], Count, Mask, Score, C) ->
|
||||||
|
case score_candidate(H) of
|
||||||
|
X when X < Score ->
|
||||||
|
select_candidate(T, Count + 1, Count + 1, X, H);
|
||||||
|
_ ->
|
||||||
|
select_candidate(T, Count + 1, Mask, Score, C)
|
||||||
|
end;
|
||||||
|
select_candidate([], _, Mask, _Score, C) ->
|
||||||
|
%?TTY({selected, Mask, {score, Score}}),
|
||||||
|
{Mask, C}.
|
||||||
|
|
||||||
|
score_candidate(C) ->
|
||||||
|
Rule1 = apply_penalty_rule_1(C),
|
||||||
|
Rule2 = apply_penalty_rule_2(C),
|
||||||
|
Rule3 = apply_penalty_rule_3(C),
|
||||||
|
Rule4 = apply_penalty_rule_4(C),
|
||||||
|
Total = Rule1 + Rule2 + Rule3 + Rule4,
|
||||||
|
%?TTY({score, Total, [Rule1, Rule2, Rule3, Rule4]}),
|
||||||
|
Total.
|
||||||
|
|
||||||
|
%% Section 8.2.2
|
||||||
|
apply_penalty_rule_1(Candidate) ->
|
||||||
|
ScoreRows = rule1(Candidate, 0),
|
||||||
|
ScoreCols = rule1(rows_to_columns(Candidate), 0),
|
||||||
|
ScoreRows + ScoreCols.
|
||||||
|
%
|
||||||
|
rule1([Row|T], Score) ->
|
||||||
|
Score0 = rule1_row(Row, Score),
|
||||||
|
rule1(T, Score0);
|
||||||
|
rule1([], Score) ->
|
||||||
|
Score.
|
||||||
|
%
|
||||||
|
rule1_row(L = [H|_], Score) ->
|
||||||
|
F = fun
|
||||||
|
(1) when H =:= 1 ->
|
||||||
|
true;
|
||||||
|
(1) ->
|
||||||
|
false;
|
||||||
|
(_) when H =:= 0 orelse is_integer(H) =:= false ->
|
||||||
|
true;
|
||||||
|
(_) ->
|
||||||
|
false
|
||||||
|
end,
|
||||||
|
{H0,T0} = lists:splitwith(F, L),
|
||||||
|
case length(H0) of
|
||||||
|
Repeats when Repeats >= 5 ->
|
||||||
|
Penalty = ?PENALTY_RULE_1 + Repeats - 5,
|
||||||
|
rule1_row(T0, Score + Penalty);
|
||||||
|
_ ->
|
||||||
|
rule1_row(T0, Score)
|
||||||
|
end;
|
||||||
|
rule1_row([], Score) ->
|
||||||
|
Score.
|
||||||
|
|
||||||
|
%% TODO
|
||||||
|
apply_penalty_rule_2(_M = [H, H0|T]) ->
|
||||||
|
% ?TTY(M),
|
||||||
|
Blocks = rule2(1, 1, H, H0, [H0|T], []),
|
||||||
|
Blocks0 = composite_blocks(Blocks, []),
|
||||||
|
Blocks1 = composite_blocks(Blocks0, []),
|
||||||
|
% ?TTY(Blocks1),
|
||||||
|
score_blocks(Blocks1, 0).
|
||||||
|
|
||||||
|
score_blocks([{_, {M, N}, _}|T], Acc) ->
|
||||||
|
Score = ?PENALTY_RULE_2 * (M - 1) * (N - 1),
|
||||||
|
score_blocks(T, Acc + Score);
|
||||||
|
score_blocks([], Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
rule2(X, Y, [H, H|T], [H, H|T0], Rows, Acc) ->
|
||||||
|
rule2(X + 1, Y, [H|T], [H|T0], Rows, [{{X, Y}, {2, 2}, H}|Acc]);
|
||||||
|
rule2(X, Y, [_|T], [_|T0], Rows, Acc) ->
|
||||||
|
rule2(X + 1, Y, T, T0, Rows, Acc);
|
||||||
|
rule2(_, Y, [], [], [H, H0|T], Acc) ->
|
||||||
|
rule2(1, Y + 1, H, H0, [H0|T], Acc);
|
||||||
|
rule2(_, _, [], [], [_], Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
composite_blocks([H|T], Acc) ->
|
||||||
|
{H0, T0} = composite_block(H, T, []),
|
||||||
|
composite_blocks(T0, [H0|Acc]);
|
||||||
|
composite_blocks([], Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
composite_block(B, [H|T], Acc) ->
|
||||||
|
case combine_block(B, H) of
|
||||||
|
false ->
|
||||||
|
composite_block(B, T, [H|Acc]);
|
||||||
|
B0 ->
|
||||||
|
composite_block(B0, T, Acc)
|
||||||
|
end;
|
||||||
|
composite_block(B, [], Acc) ->
|
||||||
|
{B, lists:reverse(Acc)}.
|
||||||
|
|
||||||
|
% Does Block 0 contain the Block 1 coordinate?
|
||||||
|
combine_block(B = {{X, Y}, {SX, SY}, _}, B0 = {{X0, Y0}, _, _})
|
||||||
|
when X0 < X + SX orelse Y0 < Y + SY ->
|
||||||
|
combine_block0(B, B0);
|
||||||
|
combine_block(_, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
% are they same valued?
|
||||||
|
combine_block0(B = {_, _, V}, B0 = {_, _, V0})
|
||||||
|
when V =:= V0 orelse (V =/= 1 andalso V0 =/= 1) ->
|
||||||
|
combine_block1(B, B0);
|
||||||
|
combine_block0(_, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
% is B extended by B0 horizontally?
|
||||||
|
combine_block1({{X, Y}, {SX, SY}, V}, {{X0, Y}, {SX0, SY}, _}) when X0 =:= X + SX - 1 ->
|
||||||
|
{{X, Y}, {SX + SX0 - 1, SY}, V};
|
||||||
|
% is B extended by B0 vertically?
|
||||||
|
combine_block1({{X, Y}, {SX, SY}, V}, {{X, Y0}, {SX, SY0}, _}) when Y0 =:= Y + SY - 1 ->
|
||||||
|
{{X, Y}, {SX, SY + SY0 - 1}, V};
|
||||||
|
combine_block1(_, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
%%
|
||||||
|
apply_penalty_rule_3(Candidate) ->
|
||||||
|
RowScores = [rule3(Row, 0) || Row <- Candidate],
|
||||||
|
ColumnScores = [rule3(Col, 0) || Col <- rows_to_columns(Candidate)],
|
||||||
|
lists:sum(RowScores) + lists:sum(ColumnScores).
|
||||||
|
%
|
||||||
|
rule3(Row = [1|T], Score) ->
|
||||||
|
Ones = lists:takewhile(fun(X) -> X =:= 1 end, Row),
|
||||||
|
Scale = length(Ones),
|
||||||
|
case Scale * 7 of
|
||||||
|
Length when Length > length(Row) ->
|
||||||
|
rule3(T, Score);
|
||||||
|
Length ->
|
||||||
|
case is_11311_pattern(lists:sublist(Row, Length), Scale) of
|
||||||
|
true ->
|
||||||
|
rule3(T, Score + ?PENALTY_RULE_3);
|
||||||
|
false ->
|
||||||
|
rule3(T, Score)
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
rule3([_|T], Score) ->
|
||||||
|
rule3(T, Score);
|
||||||
|
rule3([], Acc) ->
|
||||||
|
Acc.
|
||||||
|
%
|
||||||
|
is_11311_pattern(List, Scale) ->
|
||||||
|
List0 = lists:map(fun(X) when X =:= 1 -> 1; (_) -> 0 end, List),
|
||||||
|
Result = condense(List0, Scale, []),
|
||||||
|
Result =:= [1,0,1,1,1,0,1].
|
||||||
|
%
|
||||||
|
condense([], _, Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
condense(L, Scale, Acc) ->
|
||||||
|
{H, T} = lists:split(Scale, L),
|
||||||
|
case lists:sum(H) of
|
||||||
|
Scale ->
|
||||||
|
condense(T, Scale, [1|Acc]);
|
||||||
|
0 ->
|
||||||
|
condense(T, Scale, [0|Acc]);
|
||||||
|
_ ->
|
||||||
|
undefined
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%
|
||||||
|
apply_penalty_rule_4(Candidate) ->
|
||||||
|
Proportion = rule4(Candidate, 0, 0),
|
||||||
|
%?TTY({proportion, Proportion}),
|
||||||
|
?PENALTY_RULE_4 * (trunc(abs(Proportion * 100 - 50)) div 5).
|
||||||
|
%
|
||||||
|
rule4([H|T], Dark, All) ->
|
||||||
|
All0 = All + length(H),
|
||||||
|
Dark0 = Dark + length([X || X <- H, X =:= 1]),
|
||||||
|
rule4(T, Dark0, All0);
|
||||||
|
rule4([], Dark, All) ->
|
||||||
|
Dark / All.
|
||||||
|
|
||||||
|
%
|
||||||
|
rows_to_columns(L) ->
|
||||||
|
rows_to_columns(L, []).
|
||||||
|
rows_to_columns([[]|_], Acc) ->
|
||||||
|
lists:reverse(Acc);
|
||||||
|
rows_to_columns(L, Acc) ->
|
||||||
|
Heads = [H || [H|_] <- L],
|
||||||
|
Tails = [T || [_|T] <- L],
|
||||||
|
rows_to_columns(Tails, [Heads|Acc]).
|
||||||
|
|
263
src/qrcode_matrix.erl
Normal file
263
src/qrcode_matrix.erl
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(qrcode_matrix).
|
||||||
|
|
||||||
|
-include("qrcode_params.hrl").
|
||||||
|
|
||||||
|
-export([dimension/1, template/1, embed_data/2, overlay_static/2, finalize/5]).
|
||||||
|
|
||||||
|
-define(FINDER_BITS, <<6240274796270654599595212063015969838585429452563217548030:192>>).
|
||||||
|
|
||||||
|
%%
|
||||||
|
dimension(Version)
|
||||||
|
when Version > 0
|
||||||
|
andalso Version < 41 ->
|
||||||
|
17 + (Version * 4).
|
||||||
|
|
||||||
|
%%
|
||||||
|
template(#qr_params{version = Version, align_coords = AC}) ->
|
||||||
|
template(Version, AC).
|
||||||
|
|
||||||
|
%%
|
||||||
|
embed_data(#qr_params{version = Version, align_coords = AC, remainder = Rem}, Codewords) ->
|
||||||
|
FlippedTemplate = flip(template(Version, AC)),
|
||||||
|
FlippedMatrix = embed_data(FlippedTemplate, <<Codewords/binary, 0:Rem>>, []),
|
||||||
|
flip(FlippedMatrix).
|
||||||
|
|
||||||
|
%%
|
||||||
|
overlay_static(#qr_params{version = Version, align_coords = AC}, Matrix) ->
|
||||||
|
F = finder_bits(),
|
||||||
|
T = timing_bits(Version, AC),
|
||||||
|
A = alignment_bits(AC),
|
||||||
|
overlay_static(Matrix, F, T, A, []).
|
||||||
|
|
||||||
|
%%
|
||||||
|
finalize(Dim, FMT, VSN, QZ, Matrix) ->
|
||||||
|
M = format_bits(FMT),
|
||||||
|
V = version_bits(VSN),
|
||||||
|
FinalMatrix = overlay_format(Matrix, M, V, []),
|
||||||
|
QBitLength = (Dim + QZ * 2) * QZ,
|
||||||
|
Q = <<0:QBitLength>>,
|
||||||
|
Bin = encode_bits(FinalMatrix, QZ, Q),
|
||||||
|
<<Bin/bits, Q/bits>>.
|
||||||
|
|
||||||
|
%% Internal
|
||||||
|
|
||||||
|
%%
|
||||||
|
template(Version, AC) ->
|
||||||
|
Dim = dimension(Version),
|
||||||
|
template(1, Dim, AC, []).
|
||||||
|
%
|
||||||
|
template(Y, Max, AC, Acc) when Y =< Max->
|
||||||
|
Row = template_row(1, Y, Max, AC, []),
|
||||||
|
template(Y + 1, Max, AC, [Row|Acc]);
|
||||||
|
template(_, _, _, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
template_row(X, Y, Max, AC, Acc) when X =< Max ->
|
||||||
|
Ref = template_ref(X, Y, Max, AC),
|
||||||
|
template_row(X + 1, Y, Max, AC, [Ref|Acc]);
|
||||||
|
template_row(_, _, _, _, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
template_ref(X, Y, Max, _AC)
|
||||||
|
when (X =< 8 andalso Y =< 8)
|
||||||
|
orelse (X =< 8 andalso Y > Max - 8)
|
||||||
|
orelse (X > Max - 8 andalso Y =< 8) ->
|
||||||
|
f;
|
||||||
|
template_ref(X, Y, Max, _AC)
|
||||||
|
when (X =:= 9 andalso Y =/= 7 andalso (Y =< 9 orelse Max - Y =< 7))
|
||||||
|
orelse (Y =:= 9 andalso X =/= 7 andalso (X =< 9 orelse Max - X =< 7)) ->
|
||||||
|
m;
|
||||||
|
template_ref(X, Y, Max, _AC)
|
||||||
|
when Max >= 45
|
||||||
|
andalso ((X < 7 andalso Max - Y =< 10)
|
||||||
|
orelse (Max - X =< 10 andalso Y < 7)) ->
|
||||||
|
v;
|
||||||
|
template_ref(X, Y, Max, AC) ->
|
||||||
|
case is_alignment_bit(X, Y, AC) of
|
||||||
|
true ->
|
||||||
|
a;
|
||||||
|
false ->
|
||||||
|
template_ref0(X, Y, Max)
|
||||||
|
end.
|
||||||
|
%
|
||||||
|
template_ref0(X, Y, _)
|
||||||
|
when X =:= 7
|
||||||
|
orelse Y =:= 7 ->
|
||||||
|
t;
|
||||||
|
template_ref0(_, _, _) ->
|
||||||
|
d.
|
||||||
|
|
||||||
|
%%
|
||||||
|
is_alignment_bit(X, Y, [{Xa, Ya}|_])
|
||||||
|
when (X >= Xa - 2
|
||||||
|
andalso X =< Xa + 2
|
||||||
|
andalso Y >= Ya - 2
|
||||||
|
andalso Y =< Ya + 2) ->
|
||||||
|
true;
|
||||||
|
is_alignment_bit(X, Y, [_|T]) ->
|
||||||
|
is_alignment_bit(X, Y, T);
|
||||||
|
is_alignment_bit(_X, _Y, []) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
% deal with row 7 exceptional case
|
||||||
|
embed_data([HA, HB, H, HC, HD|T], Codewords, Acc) when length(T) =:= 4 -> % skip row 7
|
||||||
|
{HA0, HB0, Codewords0} = embed_data(HA, HB, Codewords, [], []),
|
||||||
|
{HC0, HD0, Codewords1} = embed_data_reversed(HC, HD, Codewords0),
|
||||||
|
embed_data(T, Codewords1, [HD0, HC0, H, HB0, HA0|Acc]);
|
||||||
|
% normal case
|
||||||
|
embed_data([HA, HB, HC, HD|T], Codewords, Acc) ->
|
||||||
|
{HA0, HB0, Codewords0} = embed_data(HA, HB, Codewords, [], []),
|
||||||
|
{HC0, HD0, Codewords1} = embed_data_reversed(HC, HD, Codewords0),
|
||||||
|
embed_data(T, Codewords1, [HD0, HC0, HB0, HA0|Acc]);
|
||||||
|
embed_data([], <<>>, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
embed_data([d|T0], [d|T1], <<A:1, B:1, Codewords/bits>>, StreamA, StreamB) ->
|
||||||
|
embed_data(T0, T1, Codewords, [A|StreamA], [B|StreamB]);
|
||||||
|
embed_data([d|T0], [B|T1], <<A:1, Codewords/bits>>, StreamA, StreamB) ->
|
||||||
|
embed_data(T0, T1, Codewords, [A|StreamA], [B|StreamB]);
|
||||||
|
embed_data([A|T0], [d|T1], <<B:1, Codewords/bits>>, StreamA, StreamB) ->
|
||||||
|
embed_data(T0, T1, Codewords, [A|StreamA], [B|StreamB]);
|
||||||
|
embed_data([A|T0], [B|T1], Codewords, StreamA, StreamB) ->
|
||||||
|
embed_data(T0, T1, Codewords, [A|StreamA], [B|StreamB]);
|
||||||
|
embed_data([], [], Codewords, StreamA, StreamB) ->
|
||||||
|
{lists:reverse(StreamA), lists:reverse(StreamB), Codewords}.
|
||||||
|
|
||||||
|
embed_data_reversed(A, B, Codewords) ->
|
||||||
|
{A0, B0, Codewords0} = embed_data(lists:reverse(A), lists:reverse(B), Codewords, [], []),
|
||||||
|
{lists:reverse(A0), lists:reverse(B0), Codewords0}.
|
||||||
|
|
||||||
|
%
|
||||||
|
overlay_static([H|L], F, T, A, Acc) ->
|
||||||
|
{F0, T0, A0, Row} = overlay0(H, F, T, A, []),
|
||||||
|
overlay_static(L, F0, T0, A0, [Row|Acc]);
|
||||||
|
overlay_static([], <<>>, <<>>, <<>>, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
overlay0([f|L], <<F0:1, F/bits>>, T, A, Acc) ->
|
||||||
|
overlay0(L, F, T, A, [F0|Acc]);
|
||||||
|
overlay0([t|L], F, <<T0:1, T/bits>>, A, Acc) ->
|
||||||
|
overlay0(L, F, T, A, [T0|Acc]);
|
||||||
|
overlay0([a|L], F, T, <<A0:1, A/bits>>, Acc) ->
|
||||||
|
overlay0(L, F, T, A, [A0|Acc]);
|
||||||
|
overlay0([H|L], F, T, A, Acc) ->
|
||||||
|
overlay0(L, F, T, A, [H|Acc]);
|
||||||
|
overlay0([], F, T, A, Acc) ->
|
||||||
|
{F, T, A, lists:reverse(Acc)}.
|
||||||
|
|
||||||
|
%
|
||||||
|
encode_bits([H|T], QZ, Acc) ->
|
||||||
|
Acc0 = encode_bits0(H, <<Acc/bits, 0:QZ>>),
|
||||||
|
encode_bits(T, QZ, <<Acc0/bits, 0:QZ>>);
|
||||||
|
encode_bits([], _, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
encode_bits0([H|T], Acc) when is_integer(H) ->
|
||||||
|
encode_bits0(T, <<Acc/bits, H:1>>);
|
||||||
|
encode_bits0([], Acc) ->
|
||||||
|
Acc.
|
||||||
|
%
|
||||||
|
overlay_format([H|L], M, V, Acc) ->
|
||||||
|
{M0, V0, Row} = overlay1(H, M, V, []),
|
||||||
|
overlay_format(L, M0, V0, [Row|Acc]);
|
||||||
|
overlay_format([], <<>>, <<>>, Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
%
|
||||||
|
overlay1([m|L], <<M0:1, M/bits>>, V, Acc) ->
|
||||||
|
overlay1(L, M, V, [M0|Acc]);
|
||||||
|
overlay1([v|L], M, <<V0:1, V/bits>>, Acc) ->
|
||||||
|
overlay1(L, M, V, [V0|Acc]);
|
||||||
|
overlay1([H|L], M, V, Acc) ->
|
||||||
|
overlay1(L, M, V, [H|Acc]);
|
||||||
|
overlay1([], M, V, Acc) ->
|
||||||
|
{M, V, lists:reverse(Acc)}.
|
||||||
|
|
||||||
|
%
|
||||||
|
flip(L) ->
|
||||||
|
flip(L, []).
|
||||||
|
flip([[]|T], Acc) ->
|
||||||
|
[[] || [] <- T], % guard check
|
||||||
|
[lists:reverse(L) || L <- Acc];
|
||||||
|
flip(L, Acc) ->
|
||||||
|
Heads = [H || [H|_] <- L],
|
||||||
|
Tails = [T || [_|T] <- L],
|
||||||
|
flip(Tails, [Heads|Acc]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
finder_bits() ->
|
||||||
|
?FINDER_BITS.
|
||||||
|
%%
|
||||||
|
alignment_bits(AC) ->
|
||||||
|
Repeats = composite_ac(AC, []),
|
||||||
|
alignment_bits(Repeats, <<>>).
|
||||||
|
alignment_bits([H|T], Acc) ->
|
||||||
|
Bits0 = bits:duplicate(<<31:5>>, H),
|
||||||
|
Bits1 = bits:duplicate(<<17:5>>, H),
|
||||||
|
Bits2 = bits:duplicate(<<21:5>>, H),
|
||||||
|
Bits = bits:append([Bits0, Bits1, Bits2, Bits1, Bits0]),
|
||||||
|
alignment_bits(T, <<Acc/bits, Bits/bits>>);
|
||||||
|
alignment_bits([], Acc) ->
|
||||||
|
Acc.
|
||||||
|
%
|
||||||
|
composite_ac([{_, Row}|T], Acc) ->
|
||||||
|
N = 1 + length([{X, Y} || {X, Y} <- T, Y =:= Row]),
|
||||||
|
T0 = [{X, Y} || {X, Y} <- T, Y =/= Row],
|
||||||
|
composite_ac(T0, [N|Acc]);
|
||||||
|
composite_ac([], Acc) ->
|
||||||
|
lists:reverse(Acc).
|
||||||
|
|
||||||
|
%%
|
||||||
|
timing_bits(Version, AC) ->
|
||||||
|
Length = dimension(Version) - 16,
|
||||||
|
% alignment pattern start coordinates, to trigger bit skipping
|
||||||
|
TH = timing_bits(1, Length, [X - 8 - 2 || {X, 7} <- AC], <<>>),
|
||||||
|
TV = timing_bits(1, Length, [Y - 8 - 2 || {7, Y} <- AC], <<>>),
|
||||||
|
<<TH/bits, TV/bits>>.
|
||||||
|
%
|
||||||
|
timing_bits(N, Max, A, Acc) when N =< Max ->
|
||||||
|
case lists:member(N, A) of
|
||||||
|
true -> % skip the alignment pattern
|
||||||
|
timing_bits(N + 5, Max, A, Acc);
|
||||||
|
false ->
|
||||||
|
Bit = N band 1,
|
||||||
|
timing_bits(N + 1, Max, A, <<Acc/bits, Bit:1>>)
|
||||||
|
end;
|
||||||
|
timing_bits(_, _, _, Acc) ->
|
||||||
|
Acc.
|
||||||
|
|
||||||
|
%%
|
||||||
|
format_bits(Bin) ->
|
||||||
|
<<A:7, C:1, E:7>> = bits:reverse(Bin),
|
||||||
|
<<B:8, D:7>> = Bin,
|
||||||
|
<<A:7, B:8, C:1, D:7, 1:1, E:7>>.
|
||||||
|
|
||||||
|
%%
|
||||||
|
version_bits(Bin) ->
|
||||||
|
VTop = bits:reverse(Bin),
|
||||||
|
VLeft = version_bits(VTop, []),
|
||||||
|
<<VTop/bits, VLeft/bits>>.
|
||||||
|
%
|
||||||
|
version_bits(<<X:3/bits, Bin/bits>>, Acc) ->
|
||||||
|
version_bits(Bin, [X|Acc]);
|
||||||
|
version_bits(<<>>, Acc) ->
|
||||||
|
version_bits(lists:reverse(Acc), <<>>, <<>>, <<>>).
|
||||||
|
%
|
||||||
|
version_bits([<<A:1, B:1, C:1>>|T], RowA, RowB, RowC) ->
|
||||||
|
version_bits(T, <<RowA/bits, A:1>>, <<RowB/bits, B:1>>, <<RowC/bits, C:1>>);
|
||||||
|
version_bits([], RowA, RowB, RowC) ->
|
||||||
|
bits:append([RowA, RowB, RowC]).
|
||||||
|
|
264
src/qrcode_params.hrl
Normal file
264
src/qrcode_params.hrl
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
%
|
||||||
|
% qrcode_params.hrl
|
||||||
|
|
||||||
|
-record(qr_params, {mode, version, dimension, ec_level, block_defs, align_coords, remainder, mask, data}).
|
||||||
|
|
||||||
|
-define(QR_GF256_PRIME_MODULUS, 285). % 16#011D -> 2^8 + 2^4 + 2^3 + 2^2 + 1
|
||||||
|
|
||||||
|
-define(VERSION_INFO_POLY, 7973). % 16#1f25 -> 0001 1111 0010 0101
|
||||||
|
-define(FORMAT_INFO_POLY, 1335). % 16#0537 -> 0000 0101 0011 1110
|
||||||
|
-define(FORMAT_INFO_MASK, 21522). % 16#5412 -> 0101 0100 0001 0010
|
||||||
|
|
||||||
|
-define(QUIET_ZONE, 4). % recommended value
|
||||||
|
|
||||||
|
%% Table 2. Mode Indicator
|
||||||
|
-define(TERMINATOR, 0).
|
||||||
|
-define(NUMERIC_MODE, 1).
|
||||||
|
-define(ALPHANUMERIC_MODE, 2).
|
||||||
|
-define(STRUCTURED_APPEND_MODE, 3).
|
||||||
|
-define(FNC1_FIRST_POSITION_MODE, 5).
|
||||||
|
-define(BYTE_MODE, 4).
|
||||||
|
-define(ECI_MODE, 7).
|
||||||
|
-define(KANJI_MODE, 8).
|
||||||
|
-define(FNC1_SECOND_POSITION_MODE, 9).
|
||||||
|
|
||||||
|
%% Table 3. Number of bits in Character Count Indicator
|
||||||
|
%% {Mode, [v0-v9, v10-v26, v27-v40]}
|
||||||
|
-define(CCI_BITSIZE, [
|
||||||
|
{?NUMERIC_MODE, [10, 12, 14]},
|
||||||
|
{?ALPHANUMERIC_MODE, [9, 11, 13]},
|
||||||
|
{?BYTE_MODE, [8, 16, 16]},
|
||||||
|
{?KANJI_MODE, [8, 16, 16]}
|
||||||
|
]).
|
||||||
|
|
||||||
|
% Table 5. Alphanumeric charset - see also char/1
|
||||||
|
-define(CHARSET, <<"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:">>).
|
||||||
|
-define(ALPHANUMERIC_REGEX, <<"[", ?CHARSET/binary, "]+">>).
|
||||||
|
-define(NUMERIC_REGEX, <<"[0123456789]+">>).
|
||||||
|
|
||||||
|
% Section 8.4.9
|
||||||
|
-define(DATA_PAD_0, 236). % 11101100
|
||||||
|
-define(DATA_PAD_1, 17). % 00010001
|
||||||
|
|
||||||
|
%% Annex E. Table E.1 - Origin at 1, 1 rather than 0, 0
|
||||||
|
-define(ALIGNMENT_COORDINATES, {
|
||||||
|
[],
|
||||||
|
[7, 19],
|
||||||
|
[7, 23],
|
||||||
|
[7, 27],
|
||||||
|
[7, 31],
|
||||||
|
[7, 35],
|
||||||
|
[7, 23, 39], % Version 7
|
||||||
|
[7, 25, 43],
|
||||||
|
[7, 27, 47],
|
||||||
|
[7, 29, 51],
|
||||||
|
[7, 31, 55],
|
||||||
|
[7, 33, 59],
|
||||||
|
[7, 35, 63],
|
||||||
|
[7, 27, 47, 67], % Version 14
|
||||||
|
[7, 27, 49, 71],
|
||||||
|
[7, 27, 51, 75],
|
||||||
|
[7, 31, 55, 79],
|
||||||
|
[7, 31, 57, 83],
|
||||||
|
[7, 31, 59, 87],
|
||||||
|
[7, 35, 63, 91],
|
||||||
|
[7, 29, 51, 73, 95], % Version 21
|
||||||
|
[7, 27, 51, 75, 99],
|
||||||
|
[7, 31, 55, 79, 103],
|
||||||
|
[7, 29, 55, 81, 107],
|
||||||
|
[7, 33, 59, 85, 111],
|
||||||
|
[7, 31, 59, 87, 115],
|
||||||
|
[7, 35, 63, 91, 119],
|
||||||
|
[7, 27, 51, 75, 99, 123], % Version 28
|
||||||
|
[7, 31, 55, 79, 103, 127],
|
||||||
|
[7, 27, 53, 79, 105, 131],
|
||||||
|
[7, 31, 57, 83, 109, 135],
|
||||||
|
[7, 35, 61, 87, 113, 139],
|
||||||
|
[7, 31, 59, 87, 115, 143],
|
||||||
|
[7, 35, 63, 91, 119, 147],
|
||||||
|
[7, 31, 55, 79, 103, 127, 151], % Version 35
|
||||||
|
[7, 25, 51, 77, 103, 129, 155],
|
||||||
|
[7, 29, 55, 81, 107, 133, 159],
|
||||||
|
[7, 33, 59, 85, 111, 137, 163],
|
||||||
|
[7, 27, 55, 83, 111, 139, 167],
|
||||||
|
[7, 31, 59, 87, 115, 143, 171] % Version 40
|
||||||
|
}).
|
||||||
|
|
||||||
|
% Composite of Tables 1, 7-11, 13-22
|
||||||
|
% {{level, version}, {numeric_capacity, alpha_capacity, byte_capacity, kanji_capacity}, ecc_blocks[{number_of_blocks, total_bytes, data_bytes], remainder_bits}
|
||||||
|
-define(TABLES, [
|
||||||
|
{{'L',1},{41,25,17,10},[{1,26,19}],0},
|
||||||
|
{{'L',2},{77,47,32,20},[{1,44,34}],7},
|
||||||
|
{{'L',3},{127,77,53,32},[{1,70,55}],7},
|
||||||
|
{{'L',4},{187,114,78,48},[{1,100,80}],7},
|
||||||
|
{{'L',5},{255,154,106,65},[{1,134,108}],7},
|
||||||
|
{{'L',6},{322,195,134,82},[{2,86,68}],7},
|
||||||
|
{{'L',7},{370,224,154,95},[{2,98,78}],0},
|
||||||
|
{{'L',8},{461,279,192,118},[{2,121,97}],0},
|
||||||
|
{{'L',9},{552,335,230,141},[{2,146,116}],0},
|
||||||
|
{{'L',10},{652,395,271,167},[{2,86,68},{2,87,69}],0},
|
||||||
|
{{'L',11},{772,468,321,198},[{4,101,81}],0},
|
||||||
|
{{'L',12},{883,535,367,226},[{2,116,92},{2,117,93}],0},
|
||||||
|
{{'L',13},{1022,619,425,262},[{4,133,107}],0},
|
||||||
|
{{'L',14},{1101,667,458,282},[{3,145,115},{1,146,116}],3},
|
||||||
|
{{'L',15},{1250,758,520,320},[{5,109,87},{1,110,88}],3},
|
||||||
|
{{'L',16},{1408,854,586,361},[{5,122,98},{1,123,99}],3},
|
||||||
|
{{'L',17},{1548,938,644,397},[{1,135,107},{5,136,108}],3},
|
||||||
|
{{'L',18},{1725,1046,718,442},[{5,150,120},{1,151,121}],3},
|
||||||
|
{{'L',19},{1903,1153,792,488},[{3,141,113},{4,142,114}],3},
|
||||||
|
{{'L',20},{2061,1249,858,528},[{3,135,107},{5,136,108}],3},
|
||||||
|
{{'L',21},{2232,1352,929,572},[{4,144,116},{4,145,117}],4},
|
||||||
|
{{'L',22},{2409,1460,1003,618},[{2,139,111},{7,140,112}],4},
|
||||||
|
{{'L',23},{2620,1588,1091,672},[{4,151,121},{5,152,122}],4},
|
||||||
|
{{'L',24},{2812,1704,1171,721},[{6,147,117},{4,148,118}],4},
|
||||||
|
{{'L',25},{3057,1853,1273,784},[{8,132,106},{4,133,107}],4},
|
||||||
|
{{'L',26},{3283,1990,1367,842},[{10,142,114},{2,143,115}],4},
|
||||||
|
{{'L',27},{3517,2132,1465,902},[{8,152,122},{4,153,123}],4},
|
||||||
|
{{'L',28},{3669,2223,1528,940},[{3,147,117},{10,148,118}],3},
|
||||||
|
{{'L',29},{3909,2369,1628,1002},[{7,146,116},{7,147,117}],3},
|
||||||
|
{{'L',30},{4158,2520,1732,1066},[{5,145,115},{10,146,116}],3},
|
||||||
|
{{'L',31},{4417,2677,1840,1132},[{13,145,115},{3,146,116}],3},
|
||||||
|
{{'L',32},{4686,2840,1952,1201},[{17,145,115}],3},
|
||||||
|
{{'L',33},{4965,3009,2068,1273},[{17,145,115},{1,146,116}],3},
|
||||||
|
{{'L',34},{5253,3183,2188,1347},[{13,145,115},{6,146,116}],3},
|
||||||
|
{{'L',35},{5529,3351,2303,1417},[{12,151,121},{7,152,122}],0},
|
||||||
|
{{'L',36},{5836,3537,2431,1496},[{6,151,121},{14,152,122}],0},
|
||||||
|
{{'L',37},{6153,3729,2563,1577},[{17,152,122},{4,153,123}],0},
|
||||||
|
{{'L',38},{6479,3927,2699,1661},[{4,152,122},{18,153,123}],0},
|
||||||
|
{{'L',39},{6743,4087,2809,1729},[{20,147,117},{4,148,118}],0},
|
||||||
|
{{'L',40},{7089,4296,2953,1817},[{19,148,118},{6,149,119}],0},
|
||||||
|
{{'M',1},{34,20,14,8},[{1,26,16}],0},
|
||||||
|
{{'M',2},{63,38,26,16},[{1,44,28}],7},
|
||||||
|
{{'M',3},{101,61,42,26},[{1,70,44}],7},
|
||||||
|
{{'M',4},{149,90,62,38},[{2,50,32}],7},
|
||||||
|
{{'M',5},{202,122,84,52},[{2,67,43}],7},
|
||||||
|
{{'M',6},{255,154,106,65},[{4,43,27}],7},
|
||||||
|
{{'M',7},{293,178,122,75},[{4,49,31}],0},
|
||||||
|
{{'M',8},{365,221,152,93},[{2,60,38},{2,61,39}],0},
|
||||||
|
{{'M',9},{432,262,180,111},[{3,58,36},{2,59,37}],0},
|
||||||
|
{{'M',10},{513,311,213,131},[{4,69,43},{1,70,44}],0},
|
||||||
|
{{'M',11},{604,366,251,155},[{1,80,50},{4,81,51}],0},
|
||||||
|
{{'M',12},{691,419,287,177},[{6,58,36},{2,59,37}],0},
|
||||||
|
{{'M',13},{796,483,331,204},[{8,59,37},{1,60,38}],0},
|
||||||
|
{{'M',14},{871,528,362,223},[{4,64,40},{5,65,41}],3},
|
||||||
|
{{'M',15},{991,600,412,254},[{5,65,41},{5,66,42}],3},
|
||||||
|
{{'M',16},{1082,656,450,277},[{7,73,45},{3,74,46}],3},
|
||||||
|
{{'M',17},{1212,734,504,310},[{10,74,46},{1,75,47}],3},
|
||||||
|
{{'M',18},{1346,816,560,345},[{9,69,43},{4,70,44}],3},
|
||||||
|
{{'M',19},{1500,909,624,384},[{3,70,44},{11,71,45}],3},
|
||||||
|
{{'M',20},{1600,970,666,410},[{3,67,41},{13,68,42}],3},
|
||||||
|
{{'M',21},{1708,1035,711,438},[{17,68,42}],4},
|
||||||
|
{{'M',22},{1872,1134,779,480},[{17,74,46}],4},
|
||||||
|
{{'M',23},{2059,1248,857,528},[{4,75,47},{14,76,48}],4},
|
||||||
|
{{'M',24},{2188,1326,911,561},[{6,73,45},{14,74,46}],4},
|
||||||
|
{{'M',25},{2395,1451,997,614},[{8,75,47},{13,76,48}],4},
|
||||||
|
{{'M',26},{2544,1542,1059,652},[{19,74,46},{4,75,47}],4},
|
||||||
|
{{'M',27},{2701,1637,1125,692},[{22,73,45},{3,74,46}],4},
|
||||||
|
{{'M',28},{2857,1732,1190,732},[{3,73,45},{23,74,46}],3},
|
||||||
|
{{'M',29},{3035,1839,1264,778},[{21,73,45},{7,74,46}],3},
|
||||||
|
{{'M',30},{3289,1994,1370,843},[{19,75,47},{10,76,48}],3},
|
||||||
|
{{'M',31},{3486,2113,1452,894},[{2,74,46},{29,75,47}],3},
|
||||||
|
{{'M',32},{3693,2238,1538,947},[{10,74,46},{23,75,47}],3},
|
||||||
|
{{'M',33},{3909,2369,1628,1002},[{14,74,46},{21,75,47}],3},
|
||||||
|
{{'M',34},{4134,2506,1722,1060},[{14,74,46},{23,75,47}],3},
|
||||||
|
{{'M',35},{4343,2632,1809,1113},[{12,75,47},{26,76,48}],0},
|
||||||
|
{{'M',36},{4588,2780,1911,1176},[{6,75,47},{34,76,48}],0},
|
||||||
|
{{'M',37},{4775,2894,1989,1224},[{29,74,46},{14,75,47}],0},
|
||||||
|
{{'M',38},{5039,3054,2099,1292},[{13,74,46},{32,75,47}],0},
|
||||||
|
{{'M',39},{5313,3220,2213,1362},[{40,75,47},{7,76,48}],0},
|
||||||
|
{{'M',40},{5596,3391,2331,1435},[{18,75,47},{31,76,48}],0},
|
||||||
|
{{'Q',1},{27,16,11,7},[{1,26,13}],0},
|
||||||
|
{{'Q',2},{48,29,20,12},[{1,44,22}],7},
|
||||||
|
{{'Q',3},{77,47,32,20},[{2,35,17}],7},
|
||||||
|
{{'Q',4},{111,67,46,28},[{2,50,24}],7},
|
||||||
|
{{'Q',5},{144,87,60,37},[{2,33,15},{2,34,16}],7},
|
||||||
|
{{'Q',6},{178,108,74,45},[{4,43,19}],7},
|
||||||
|
{{'Q',7},{207,125,86,53},[{2,32,14},{4,33,15}],0},
|
||||||
|
{{'Q',8},{259,157,108,66},[{4,40,18},{2,41,19}],0},
|
||||||
|
{{'Q',9},{312,189,130,80},[{4,36,16},{4,37,17}],0},
|
||||||
|
{{'Q',10},{364,221,151,93},[{6,43,19},{2,44,20}],0},
|
||||||
|
{{'Q',11},{427,259,177,109},[{4,50,22},{4,51,23}],0},
|
||||||
|
{{'Q',12},{489,296,203,125},[{4,46,20},{6,47,21}],0},
|
||||||
|
{{'Q',13},{580,352,241,149},[{8,44,20},{4,45,21}],0},
|
||||||
|
{{'Q',14},{621,376,258,159},[{11,36,16},{5,37,17}],3},
|
||||||
|
{{'Q',15},{703,426,292,180},[{5,54,24},{7,55,25}],3},
|
||||||
|
{{'Q',16},{775,470,322,198},[{15,43,19},{2,44,20}],3},
|
||||||
|
{{'Q',17},{876,531,364,224},[{1,50,22},{15,51,23}],3},
|
||||||
|
{{'Q',18},{948,574,394,243},[{17,50,22},{1,51,23}],3},
|
||||||
|
{{'Q',19},{1063,644,442,272},[{17,47,21},{4,48,22}],3},
|
||||||
|
{{'Q',20},{1159,702,482,297},[{15,54,24},{5,55,25}],3},
|
||||||
|
{{'Q',21},{1224,742,509,314},[{17,50,22},{6,51,23}],4},
|
||||||
|
{{'Q',22},{1358,823,565,348},[{7,54,24},{16,55,25}],4},
|
||||||
|
{{'Q',23},{1468,890,611,376},[{11,54,24},{14,55,25}],4},
|
||||||
|
{{'Q',24},{1588,963,661,407},[{11,54,24},{16,55,25}],4},
|
||||||
|
{{'Q',25},{1718,1041,715,440},[{7,54,24},{22,55,25}],4},
|
||||||
|
{{'Q',26},{1804,1094,751,462},[{28,50,22},{6,51,23}],4},
|
||||||
|
{{'Q',27},{1933,1172,805,496},[{8,53,23},{26,54,24}],4},
|
||||||
|
{{'Q',28},{2085,1263,868,534},[{4,54,24},{31,55,25}],3},
|
||||||
|
{{'Q',29},{2181,1322,908,559},[{1,53,23},{37,54,24}],3},
|
||||||
|
{{'Q',30},{2358,1429,982,604},[{15,54,24},{25,55,25}],3},
|
||||||
|
{{'Q',31},{2473,1499,1030,634},[{42,54,24},{1,55,25}],3},
|
||||||
|
{{'Q',32},{2670,1618,1112,684},[{10,54,24},{35,55,25}],3},
|
||||||
|
{{'Q',33},{2805,1700,1168,719},[{29,54,24},{19,55,25}],3},
|
||||||
|
{{'Q',34},{2949,1787,1228,756},[{44,54,24},{7,55,25}],3},
|
||||||
|
{{'Q',35},{3081,1867,1283,790},[{39,54,24},{14,55,25}],0},
|
||||||
|
{{'Q',36},{3244,1966,1351,832},[{46,54,24},{10,55,25}],0},
|
||||||
|
{{'Q',37},{3417,2071,1423,876},[{49,54,24},{10,55,25}],0},
|
||||||
|
{{'Q',38},{3599,2181,1499,923},[{48,54,24},{14,55,25}],0},
|
||||||
|
{{'Q',39},{3791,2298,1579,972},[{43,54,24},{22,55,25}],0},
|
||||||
|
{{'Q',40},{3993,2420,1663,1024},[{34,54,24},{34,55,25}],0},
|
||||||
|
{{'H',1},{17,10,7,4},[{1,26,9}],0},
|
||||||
|
{{'H',2},{34,20,14,8},[{1,44,16}],7},
|
||||||
|
{{'H',3},{58,35,24,15},[{2,35,13}],7},
|
||||||
|
{{'H',4},{82,50,34,21},[{4,25,9}],7},
|
||||||
|
{{'H',5},{106,64,44,27},[{2,33,11},{2,34,12}],7},
|
||||||
|
{{'H',6},{139,84,58,36},[{4,43,15}],7},
|
||||||
|
{{'H',7},{154,93,64,39},[{4,39,13},{1,40,14}],0},
|
||||||
|
{{'H',8},{202,122,84,52},[{4,40,14},{2,41,15}],0},
|
||||||
|
{{'H',9},{235,143,98,60},[{4,36,12},{4,37,13}],0},
|
||||||
|
{{'H',10},{288,174,119,74},[{6,43,15},{2,44,16}],0},
|
||||||
|
{{'H',11},{331,200,137,85},[{3,36,12},{8,37,13}],0},
|
||||||
|
{{'H',12},{374,227,155,96},[{7,42,14},{4,43,15}],0},
|
||||||
|
{{'H',13},{427,259,177,109},[{12,33,11},{4,34,12}],0},
|
||||||
|
{{'H',14},{468,283,194,120},[{11,36,12},{5,37,13}],3},
|
||||||
|
{{'H',15},{530,321,220,136},[{11,36,12},{7,37,13}],3},
|
||||||
|
{{'H',16},{602,365,250,154},[{3,45,15},{13,46,16}],3},
|
||||||
|
{{'H',17},{674,408,280,173},[{2,42,14},{17,43,15}],3},
|
||||||
|
{{'H',18},{746,452,310,191},[{2,42,14},{19,43,15}],3},
|
||||||
|
{{'H',19},{813,493,338,208},[{9,39,13},{16,40,14}],3},
|
||||||
|
{{'H',20},{919,557,382,235},[{15,43,15},{10,44,16}],3},
|
||||||
|
{{'H',21},{969,587,403,248},[{19,46,16},{6,47,17}],4},
|
||||||
|
{{'H',22},{1056,640,439,270},[{34,37,13}],4},
|
||||||
|
{{'H',23},{1108,672,461,284},[{16,45,15},{14,46,16}],4},
|
||||||
|
{{'H',24},{1228,744,511,315},[{30,46,16},{2,47,17}],4},
|
||||||
|
{{'H',25},{1286,779,535,330},[{22,45,15},{13,46,16}],4},
|
||||||
|
{{'H',26},{1425,864,593,365},[{33,46,16},{4,47,17}],4},
|
||||||
|
{{'H',27},{1501,910,625,385},[{12,45,15},{28,46,16}],4},
|
||||||
|
{{'H',28},{1581,958,658,405},[{11,45,15},{31,46,16}],3},
|
||||||
|
{{'H',29},{1677,1016,698,430},[{19,45,15},{26,46,16}],3},
|
||||||
|
{{'H',30},{1782,1080,742,457},[{23,45,15},{25,46,16}],3},
|
||||||
|
{{'H',31},{1897,1150,790,486},[{23,45,15},{28,46,16}],3},
|
||||||
|
{{'H',32},{2022,1226,842,518},[{19,45,15},{35,46,16}],3},
|
||||||
|
{{'H',33},{2157,1307,898,553},[{11,45,15},{46,46,16}],3},
|
||||||
|
{{'H',34},{2301,1394,958,590},[{59,46,16},{1,47,17}],3},
|
||||||
|
{{'H',35},{2361,1431,983,605},[{22,45,15},{41,46,16}],0},
|
||||||
|
{{'H',36},{2524,1530,1051,647},[{2,45,15},{64,46,16}],0},
|
||||||
|
{{'H',37},{2625,1591,1093,673},[{24,45,15},{46,46,16}],0},
|
||||||
|
{{'H',38},{2735,1658,1139,701},[{42,45,15},{32,46,16}],0},
|
||||||
|
{{'H',39},{2927,1774,1219,750},[{10,45,15},{67,46,16}],0},
|
||||||
|
{{'H',40},{3057,1852,1273,784},[{20,45,15},{61,46,16}],0}
|
||||||
|
]).
|
||||||
|
|
69
src/qrcode_reedsolomon.erl
Normal file
69
src/qrcode_reedsolomon.erl
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
%% Copyright 2011 Steve Davis <steve@simulacity.com>
|
||||||
|
%
|
||||||
|
% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
% you may not use this file except in compliance with the License.
|
||||||
|
% You may obtain a copy of the License at
|
||||||
|
%
|
||||||
|
% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%
|
||||||
|
% Unless required by applicable law or agreed to in writing, software
|
||||||
|
% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
% See the License for the specific language governing permissions and
|
||||||
|
% limitations under the License.
|
||||||
|
|
||||||
|
-module(qrcode_reedsolomon).
|
||||||
|
|
||||||
|
-export([encode/2, bch_code/2]).
|
||||||
|
|
||||||
|
-define(QRCODE_GF256_PRIME_MODULUS, 285). % 16#011D = 2^8 + 2^4 + 2^3 + 2^2 + 2^0
|
||||||
|
|
||||||
|
%%
|
||||||
|
encode(Bin, Degree) when Degree > 0 ->
|
||||||
|
Field = gf256:field(?QRCODE_GF256_PRIME_MODULUS),
|
||||||
|
Generator = generator(Field, Degree),
|
||||||
|
Data = binary_to_list(Bin),
|
||||||
|
Coeffs = gf256:monomial_product(Field, Data, 1, Degree),
|
||||||
|
{_Quotient, Remainder} = gf256:divide(Field, Coeffs, Generator),
|
||||||
|
ErrorCorrectionBytes = list_to_binary(Remainder),
|
||||||
|
<<ErrorCorrectionBytes/binary>>.
|
||||||
|
|
||||||
|
%%
|
||||||
|
bch_code(Byte, Poly) ->
|
||||||
|
MSB = msb(Poly),
|
||||||
|
Byte0 = Byte bsl (MSB - 1),
|
||||||
|
bch_code(Byte0, Poly, MSB).
|
||||||
|
|
||||||
|
|
||||||
|
%% Internal
|
||||||
|
|
||||||
|
%%
|
||||||
|
generator(F, D) when D > 0 ->
|
||||||
|
generator(F, [1], D, 0).
|
||||||
|
%
|
||||||
|
generator(_, P, D, D) ->
|
||||||
|
P;
|
||||||
|
generator(F, P, D, Count) ->
|
||||||
|
P0 = gf256:polynomial_product(F, P, [1, gf256:exponent(F, Count)]),
|
||||||
|
generator(F, P0, D, Count + 1).
|
||||||
|
|
||||||
|
%
|
||||||
|
bch_code(Byte, Poly, MSB) ->
|
||||||
|
case msb(Byte) >= MSB of
|
||||||
|
true ->
|
||||||
|
Byte0 = Byte bxor (Poly bsl (msb(Byte) - MSB)),
|
||||||
|
bch_code(Byte0, Poly, MSB);
|
||||||
|
false ->
|
||||||
|
Byte
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%
|
||||||
|
msb(0) ->
|
||||||
|
0;
|
||||||
|
msb(Byte) ->
|
||||||
|
msb(Byte, 0).
|
||||||
|
msb(0, Count) ->
|
||||||
|
Count;
|
||||||
|
msb(Byte, Count) ->
|
||||||
|
msb(Byte bsr 1, Count + 1).
|
||||||
|
|
Loading…
Reference in New Issue
Block a user