WireguardConfigGen/qr/matrix.js

353 lines
9.9 KiB
JavaScript

"use strict";
// {{{1 Initialize matrix with zeros
function init(version) {
var N = version * 4 + 17;
var matrix = [];
var zeros = new Buffer(N);
zeros.fill(0);
zeros = [].slice.call(zeros);
for (var i = 0; i < N; i++) {
matrix[i] = zeros.slice();
}
return matrix;
}
// {{{1 Put finders into matrix
function fillFinders(matrix) {
var N = matrix.length;
for (var i = -3; i <= 3; i++) {
for (var j = -3; j <= 3; j++) {
var max = Math.max(i, j);
var min = Math.min(i, j);
var pixel = (max == 2 && min >= -2) || (min == -2 && max <= 2) ? 0x80 : 0x81;
matrix[3 + i][3 + j] = pixel;
matrix[3 + i][N - 4 + j] = pixel;
matrix[N - 4 + i][3 + j] = pixel;
}
}
for (var i = 0; i < 8; i++) {
matrix[7][i] = matrix[i][7] =
matrix[7][N - i - 1] = matrix[i][N - 8] =
matrix[N - 8][i] = matrix[N - 1 - i][7] = 0x80;
}
}
// {{{1 Put align and timinig
function fillAlignAndTiming(matrix) {
var N = matrix.length;
if (N > 21) {
var len = N - 13;
var delta = Math.round(len / Math.ceil(len / 28));
if (delta % 2) delta++;
var res = [];
for (var p = len + 6; p > 10; p -= delta) {
res.unshift(p);
}
res.unshift(6);
for (var i = 0; i < res.length; i++) {
for (var j = 0; j < res.length; j++) {
var x = res[i], y = res[j];
if (matrix[x][y]) continue;
for (var r = -2; r <=2 ; r++) {
for (var c = -2; c <=2 ; c++) {
var max = Math.max(r, c);
var min = Math.min(r, c);
var pixel = (max == 1 && min >= -1) || (min == -1 && max <= 1) ? 0x80 : 0x81;
matrix[x + r][y + c] = pixel;
}
}
}
}
}
for (var i = 8; i < N - 8; i++) {
matrix[6][i] = matrix[i][6] = i % 2 ? 0x80 : 0x81;
}
}
// {{{1 Fill reserved areas with zeroes
function fillStub(matrix) {
var N = matrix.length;
for (var i = 0; i < 8; i++) {
if (i != 6) {
matrix[8][i] = matrix[i][8] = 0x80;
}
matrix[8][N - 1 - i] = 0x80;
matrix[N - 1 - i][8] = 0x80;
}
matrix[8][8] = 0x80;
matrix[N - 8][8] = 0x81;
if (N < 45) return;
for (var i = N - 11; i < N - 8; i++) {
for (var j = 0; j < 6; j++) {
matrix[i][j] = matrix[j][i] = 0x80;
}
}
}
// {{{1 Fill reserved areas
var fillReserved = (function() {
var FORMATS = Array(32);
var VERSIONS = Array(40);
var gf15 = 0x0537;
var gf18 = 0x1f25;
var formats_mask = 0x5412;
for (var format = 0; format < 32; format++) {
var res = format << 10;
for (var i = 5; i > 0; i--) {
if (res >>> (9 + i)) {
res = res ^ (gf15 << (i - 1));
}
}
FORMATS[format] = (res | (format << 10)) ^ formats_mask;
}
for (var version = 7; version <= 40; version++) {
var res = version << 12;
for (var i = 6; i > 0; i--) {
if (res >>> (11 + i)) {
res = res ^ (gf18 << (i - 1));
}
}
VERSIONS[version] = (res | (version << 12));
}
var EC_LEVELS = { L: 1, M: 0, Q: 3, H: 2 };
return function fillReserved(matrix, ec_level, mask) {
var N = matrix.length;
var format = FORMATS[EC_LEVELS[ec_level] << 3 | mask];
function F(k) { return format >> k & 1 ? 0x81 : 0x80 };
for (var i = 0; i < 8; i++) {
matrix[8][N - 1 - i] = F(i);
if (i < 6) matrix[i][8] = F(i);
}
for (var i = 8; i < 15; i++) {
matrix[N - 15 + i][8] = F(i);
if (i > 8) matrix[8][14 - i] = F(i);
}
matrix[7][8] = F(6);
matrix[8][8] = F(7);
matrix[8][7] = F(8);
var version = VERSIONS[(N - 17)/4];
if (!version) return;
function V(k) { return version >> k & 1 ? 0x81 : 0x80 };
for (var i = 0; i < 6; i++) {
for (var j = 0; j < 3; j++) {
matrix[N - 11 + j][i] = matrix[i][N - 11 + j] = V(i * 3 + j);
}
}
}
})();
// {{{1 Fill data
var fillData = (function() {
var MASK_FUNCTIONS = [
function(i, j) { return (i + j) % 2 == 0 },
function(i, j) { return i % 2 == 0 },
function(i, j) { return j % 3 == 0 },
function(i, j) { return (i + j) % 3 == 0 },
function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0 },
function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0 },
function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0 },
function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0 }
];
return function fillData(matrix, data, mask) {
var N = matrix.length;
var row, col, dir = -1;
row = col = N - 1;
var mask_fn = MASK_FUNCTIONS[mask];
var len = data.blocks[data.blocks.length - 1].length;
for (var i = 0; i < len; i++) {
for (var b = 0; b < data.blocks.length; b++) {
if (data.blocks[b].length <= i) continue;
put(data.blocks[b][i]);
}
}
len = data.ec_len;
for (var i = 0; i < len; i++) {
for (var b = 0; b < data.ec.length; b++) {
put(data.ec[b][i]);
}
}
if (col > -1) {
do {
matrix[row][col] = mask_fn(row, col) ? 1 : 0;
} while (next());
}
function put(byte) {
for (var mask = 0x80; mask; mask = mask >> 1) {
var pixel = !!(mask & byte);
if (mask_fn(row, col)) pixel = !pixel;
matrix[row][col] = pixel ? 1 : 0;
next();
}
}
function next() {
do {
if ((col % 2) ^ (col < 6)) {
if (dir < 0 && row == 0 || dir > 0 && row == N - 1) {
col--;
dir = -dir;
} else {
col++;
row += dir;
}
} else {
col--;
}
if (col == 6) {
col--;
}
if (col < 0) {
return false;
}
} while (matrix[row][col] & 0xf0);
return true;
}
}
})();
// {{{1 Calculate penalty
function calculatePenalty(matrix) {
var N = matrix.length;
var penalty = 0;
// Rule 1
for (var i = 0; i < N; i++) {
var pixel = matrix[i][0] & 1;
var len = 1;
for (var j = 1; j < N; j++) {
var p = matrix[i][j] & 1;
if (p == pixel) {
len++;
continue;
}
if (len >= 5) {
penalty += len - 2;
}
pixel = p;
len = 1;
}
if (len >= 5) {
penalty += len - 2;
}
}
for (var j = 0; j < N; j++) {
var pixel = matrix[0][j] & 1;
var len = 1;
for (var i = 1; i < N; i++) {
var p = matrix[i][j] & 1;
if (p == pixel) {
len++;
continue;
}
if (len >= 5) {
penalty += len - 2;
}
pixel = p;
len = 1;
}
if (len >= 5) {
penalty += len - 2;
}
}
// Rule 2
for (var i = 0; i < N - 1; i++) {
for (var j = 0; j < N - 1; j++) {
var s = matrix[i][j] + matrix[i][j + 1] + matrix[i + 1][j] + matrix[i + 1][j + 1] & 7;
if (s == 0 || s == 4) {
penalty += 3;
}
}
}
// Rule 3
function I(k) { return matrix[i][j + k] & 1 };
function J(k) { return matrix[i + k][j] & 1 };
for (var i = 0; i < N; i++) {
for (var j = 0; j < N; j++) {
if (j < N - 6 && I(0) && !I(1) && I(2) && I(3) && I(4) && !I(5) && I(6)) {
if (j >= 4 && !(I(-4) || I(-3) || I(-2) || I(-1))) {
penalty += 40;
}
if (j < N - 10 && !(I(7) || I(8) || I(9) || I(10))) {
penalty += 40;
}
}
if (i < N - 6 && J(0) && !J(1) && J(2) && J(3) && J(4) && !J(5) && J(6)) {
if (i >= 4 && !(J(-4) || J(-3) || J(-2) || J(-1))) {
penalty += 40;
}
if (i < N - 10 && !(J(7) || J(8) || J(9) || J(10))) {
penalty += 40;
}
}
}
}
// Rule 4
var numDark = 0;
for (var i = 0; i < N; i++) {
for (var j = 0; j < N; j++) {
if (matrix[i][j] & 1) numDark++;
}
}
penalty += 10 * Math.floor(Math.abs(10 - 20 * numDark/(N * N)));
return penalty;
}
// {{{1 All-in-one function
function getMatrix(data) {
var matrix = init(data.version);
fillFinders(matrix);
fillAlignAndTiming(matrix);
fillStub(matrix);
var penalty = Infinity;
var bestMask = 0;
for (var mask = 0; mask < 8; mask++) {
fillData(matrix, data, mask);
fillReserved(matrix, data.ec_level, mask);
var p = calculatePenalty(matrix);
if (p < penalty) {
penalty = p;
bestMask = mask;
}
}
fillData(matrix, data, bestMask);
fillReserved(matrix, data.ec_level, bestMask);
return matrix.map(function(row) {
return row.map(function(cell) {
return cell & 1;
});
});
}
// {{{1 export functions
module.exports = {
getMatrix: getMatrix,
init: init,
fillFinders: fillFinders,
fillAlignAndTiming: fillAlignAndTiming,
fillStub: fillStub,
fillReserved: fillReserved,
fillData: fillData,
calculatePenalty: calculatePenalty,
}