UNPKG

qr-image

Version:

QR Code generator (png, svg, pdf, eps)

353 lines (321 loc) 10.2 kB
"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, }