Packages
qr_code_term
0.1.0
QRCode is a library for Elixir to create byte-mode QR codes and print them in terminal.
Current section
Files
Jump to
Current section
Files
lib/mask.ex
defmodule QRCode.Mask do
#generate
#select
# todo: https://github.com/komone/qrcode/blob/master/src/qrcode_mask.erl
alias QRCode.Params
require Bitwise
@penalty_rule_1 3
@penalty_rule_2 3
@penalty_rule_3 40
@penalty_rule_4 10
def generate(%Params{dimension: dimension}, matrix) do
0..7
|> Enum.map(fn x -> mask(x) end)
|> Enum.map(fn mf -> generate_mask(dimension, mf) end)
|> Enum.map(fn mask -> apply_mask(matrix, mask, []) end)
end
def select([h | t]) do
score = score_candidate(h)
select_candidate(t, 0, 0, score, h)
end
defp generate_mask(max, mf) do
seq = 0..(max - 1)
Enum.map(seq, fn y -> generate_mask(seq, y, mf) end)
end
defp generate_mask(seq, y, mf) do
Enum.map(seq, fn x ->
case mf.(x, y) do
true -> 1
false -> 0
end
end)
end
defp apply_mask([h|t], [h0|t0], acc) do
row = apply_mask0(h, h0, [])
apply_mask(t, t0, [row|acc])
end
defp apply_mask([], [], acc), do: Enum.reverse(acc)
defp apply_mask0([h|t], [h0|t0], acc) when is_integer(h), do:
apply_mask0(t, t0, [Bitwise.bxor(h, h0) | acc])
defp apply_mask0([h|t], [_|t0], acc), do:
apply_mask0(t, t0, [h|acc])
defp apply_mask0([], [], acc), do: Enum.reverse(acc)
defp mask(0), do: fn x, y -> rem(x + y, 2) == 0 end
defp mask(1), do: fn _, y -> rem(y, 2) == 0 end
defp mask(2), do: fn x, _ -> rem(x, 3) == 0 end
defp mask(3), do: fn x, y -> rem(x + y, 3) == 0 end
defp mask(4), do: fn x, y -> rem(div(x, 3) + div(y, 2), 2) == 0 end
defp mask(5), do: fn x, y ->
sum = x * y
rem(sum, 2) + rem(sum, 3) == 0
end
defp mask(6), do: fn x, y ->
sum = x * y
rem(rem(sum, 2) + rem(sum, 3), 2) == 0
end
defp mask(7), do: fn x, y ->
rem(rem(x * y, 3) + rem(x + y, 2), 2) == 0 end
defp select_candidate([h|t], count, mask, score, c) do
case score_candidate(h) do
x when x < score -> select_candidate(t, count + 1, count + 1, x, h)
_ -> select_candidate(t, count + 1, mask, score, c)
end
end
defp select_candidate([], _, mask, _score, c), do:
{mask, c}
defp score_candidate(c) do
rule1 = apply_penalty_rule_1(c)
rule2 = apply_penalty_rule_2(c)
rule3 = apply_penalty_rule_3(c)
rule4 = apply_penalty_rule_4(c)
rule1 + rule2 + rule3 + rule4
end
defp apply_penalty_rule_1(candidate) do
score_rows = rule1(candidate, 0)
score_cols = rule1(rows_to_columns(candidate), 0)
score_cols + score_rows
end
defp rule1([row|t], score) do
score = rule1_row(row, score)
rule1(t, score)
end
defp rule1([], score), do: score
defp rule1_row(l = [h|_], score) do
f = fn
1 when h == 1 -> true
1 -> false
_ when h == 0 or not is_integer(h) -> true
_ -> false
end
{h, t} = Enum.split_with(l, f)
case length(h) do
repeats when repeats >= 5 ->
penalty = @penalty_rule_1 + repeats - 5
rule1_row(t, score + penalty)
_ -> rule1_row(t, score)
end
end
defp rule1_row([], score), do: score
defp apply_penalty_rule_2([h, h0 | t]) do
rule2(1, 1, h, h0, [h0|t], [])
|> composite_blocks([])
|> composite_blocks([])
|> Enum.reduce(0, fn
{_, {m, n}, _}, acc ->
@penalty_rule_2 * (m - 1) * (n - 1) + acc
end)
end
defp rule2(x, y, [h, h | t], [h, h | t0], rows, acc), do:
rule2(x + 1, y, [h|t], [h|t0], rows, [{{x,y}, {2,2}, h} | acc])
defp rule2(x, y, [_|t], [_|t0], rows, acc), do:
rule2(x + 1, y, t, t0, rows, acc)
defp rule2(_, y, [], [], [h, h0|t], acc), do:
rule2(1, y + 1, h, h0, [h0|t], acc)
defp rule2(_, _, [], [], [_], acc), do: Enum.reverse(acc)
defp composite_blocks([h|t], acc) do
{h, t} = composite_block(h, t, [])
composite_blocks(t, [h|acc])
end
defp composite_blocks([], acc), do: Enum.reverse(acc)
defp composite_block(b, [h|t], acc) do
case combine_block(b, h) do
false -> composite_block(b, t, [h|acc])
b -> composite_block(b, t, acc)
end
end
defp composite_block(b, [], acc), do:
{b, Enum.reverse(acc)}
defp combine_block(b = {{x, y}, {sx, sy}, _}, b0 = {{x0, y0}, _, _})
when x0 < x + sx or y0 < y + sy, do:
combine_block0(b, b0)
defp combine_block(_, _), do: false
defp combine_block0(b = {_, _, v}, b0 = {_, _, v0})
when v == v0 or (v != 1 and v0 != 1), do:
combine_block1(b, b0)
defp combine_block0(_, _), do: false
defp combine_block1({{x, y}, {sx, sy}, v}, {{x0, y}, {sx0, sy}, _}) when x0 == x + sx - 1 do
{{x, y}, {sx + sx0 - 1, sy}, v}
end
defp combine_block1({{x, y}, {sx, sy}, v}, {{x, y0}, {sx, sy0}, _}) when y0 == y + sy - 1 do
{{x, y}, {sx, sy + sy0 - 1}, v}
end
defp combine_block1(_, _), do: false
defp apply_penalty_rule_3(candidate) do
row_scores = candidate
|> Enum.map(fn row -> rule3(row, 0) end)
column_scores = candidate
|> rows_to_columns()
|> Enum.map(fn row -> rule3(row, 0) end)
Enum.sum(row_scores) + Enum.sum(column_scores)
end
defp rule3(row = [1|t], score) do
ones = Enum.take_while(row, fn x -> x == 1 end)
scale = length(ones)
case scale * 7 do
length when length > length(row) -> rule3(t, score)
length ->
case is_11311_pattern(Enum.slice(row, 0, length), scale) do
true -> rule3(t, score + @penalty_rule_3)
false -> rule3(t, score)
end
end
end
defp rule3([_|t], acc), do: rule3(t, acc)
defp rule3([], acc), do: acc
defp is_11311_pattern(list, scale) do
list = Enum.map(list, fn
x when x == 1 -> 1
_ -> 0
end)
result = condense(list, scale, [])
result == [1, 0, 1, 1, 1, 0, 1]
end
defp condense([], _, acc), do: Enum.reverse(acc)
defp condense(l, scale, acc) do
{h, t} = Enum.split(l, scale)
case Enum.sum(h) do
^scale -> condense(t, scale, [1|acc])
0 -> condense(t, scale, [0|acc])
_ -> nil
end
end
defp apply_penalty_rule_4(candidate) do
proportion = rule4(candidate, 0, 0)
@penalty_rule_4 * div(trunc(abs(proportion * 100 - 50)), 5)
end
defp rule4([h|t], dark, all) do
all = all + length(h)
dark = dark + Enum.count(h, fn x -> x == 1 end)
rule4(t, dark, all)
end
defp rule4([], dark, all), do: dark / all
defp rows_to_columns(l), do: rows_to_columns(l, [])
defp rows_to_columns([[]|_], acc), do: Enum.reverse(acc)
defp rows_to_columns(l, acc) do
heads = Enum.map(l, fn [h|_] -> h end)
tails = Enum.map(l, fn [_|t] -> t end)
rows_to_columns(tails, [heads|acc])
end
end