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:
komone
2011-04-10 23:08:14 -05:00
commit 3939fb66e7
15 changed files with 1612 additions and 0 deletions

79
src/base32.erl Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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}
]).

View 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).