cnf-qrcode
Version:
generate qrcode,support svg base64 utf8
243 lines (211 loc) • 5.94 kB
JavaScript
/**
* Data mask pattern reference
* @type {Object}
*/
export const Patterns = {
PATTERN000: 0,
PATTERN001: 1,
PATTERN010: 2,
PATTERN011: 3,
PATTERN100: 4,
PATTERN101: 5,
PATTERN110: 6,
PATTERN111: 7,
};
/**
* Weighted penalty scores for the undesirable features
* @type {Object}
*/
const PenaltyScores = {
N1: 3,
N2: 3,
N3: 40,
N4: 10,
};
/**
* Check if mask pattern value is valid
*
* @param {Number} mask Mask pattern
* @return {Boolean} true if valid, false otherwise
*/
export function isValid(mask) {
return mask != null && mask !== '' && !isNaN(mask) && mask >= 0 && mask <= 7;
}
/**
* Returns mask pattern from a value.
* If value is not valid, returns undefined
*
* @param {Number|String} value Mask pattern value
* @return {Number} Valid mask pattern or undefined
*/
export function from(value) {
return isValid(value) ? parseInt(value, 10) : undefined;
}
/**
* Find adjacent modules in row/column with the same color
* and assign a penalty value.
*
* Points: N1 + i
* i is the amount by which the number of adjacent modules of the same color exceeds 5
*/
export function getPenaltyN1(data) {
const { size } = data;
let points = 0;
let sameCountCol = 0;
let sameCountRow = 0;
let lastCol = null;
let lastRow = null;
for (let row = 0; row < size; row++) {
sameCountCol = sameCountRow = 0;
lastCol = lastRow = null;
for (let col = 0; col < size; col++) {
let module = data.get(row, col);
if (module === lastCol) {
sameCountCol++;
} else {
if (sameCountCol >= 5) points += PenaltyScores.N1 + (sameCountCol - 5);
lastCol = module;
sameCountCol = 1;
}
module = data.get(col, row);
if (module === lastRow) {
sameCountRow++;
} else {
if (sameCountRow >= 5) points += PenaltyScores.N1 + (sameCountRow - 5);
lastRow = module;
sameCountRow = 1;
}
}
if (sameCountCol >= 5) points += PenaltyScores.N1 + (sameCountCol - 5);
if (sameCountRow >= 5) points += PenaltyScores.N1 + (sameCountRow - 5);
}
return points;
}
/**
* Find 2x2 blocks with the same color and assign a penalty value
*
* Points: N2 * (m - 1) * (n - 1)
*/
export function getPenaltyN2(data) {
const { size } = data;
let points = 0;
for (let row = 0; row < size - 1; row++) {
for (let col = 0; col < size - 1; col++) {
const last = data.get(row, col)
+ data.get(row, col + 1)
+ data.get(row + 1, col)
+ data.get(row + 1, col + 1);
if (last === 4 || last === 0) points++;
}
}
return points * PenaltyScores.N2;
}
/**
* Find 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column,
* preceded or followed by light area 4 modules wide
*
* Points: N3 * number of pattern found
*/
export function getPenaltyN3(data) {
const { size } = data;
let points = 0;
let bitsCol = 0;
let bitsRow = 0;
for (let row = 0; row < size; row++) {
bitsCol = bitsRow = 0;
for (let col = 0; col < size; col++) {
bitsCol = ((bitsCol << 1) & 0x7FF) | data.get(row, col);
if (col >= 10 && (bitsCol === 0x5D0 || bitsCol === 0x05D)) points++;
bitsRow = ((bitsRow << 1) & 0x7FF) | data.get(col, row);
if (col >= 10 && (bitsRow === 0x5D0 || bitsRow === 0x05D)) points++;
}
}
return points * PenaltyScores.N3;
}
/**
* Calculate proportion of dark modules in entire symbol
*
* Points: N4 * k
*
* k is the rating of the deviation of the proportion of dark modules
* in the symbol from 50% in steps of 5%
*/
export function getPenaltyN4(data) {
let darkCount = 0;
const modulesCount = data.data.length;
for (let i = 0; i < modulesCount; i++) darkCount += data.data[i];
const k = Math.abs(Math.ceil((darkCount * 100 / modulesCount) / 5) - 10);
return k * PenaltyScores.N4;
}
/**
* Return mask value at given position
*
* @param {Number} maskPattern Pattern reference value
* @param {Number} i Row
* @param {Number} j Column
* @return {Boolean} Mask value
*/
function getMaskAt(maskPattern, i, j) {
switch (maskPattern) {
case Patterns.PATTERN000:
return (i + j) % 2 === 0;
case Patterns.PATTERN001:
return i % 2 === 0;
case Patterns.PATTERN010:
return j % 3 === 0;
case Patterns.PATTERN011:
return (i + j) % 3 === 0;
case Patterns.PATTERN100:
return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0;
case Patterns.PATTERN101:
return (i * j) % 2 + (i * j) % 3 === 0;
case Patterns.PATTERN110:
return ((i * j) % 2 + (i * j) % 3) % 2 === 0;
case Patterns.PATTERN111:
return ((i * j) % 3 + (i + j) % 2) % 2 === 0;
default:
throw new Error(`bad maskPattern:${maskPattern}`);
}
}
/**
* Apply a mask pattern to a BitMatrix
*
* @param {Number} pattern Pattern reference number
* @param {BitMatrix} data BitMatrix data
*/
export function applyMask(pattern, data) {
const { size } = data;
for (let col = 0; col < size; col++) {
for (let row = 0; row < size; row++) {
if (data.isReserved(row, col)) continue;
data.xor(row, col, getMaskAt(pattern, row, col));
}
}
}
/**
* Returns the best mask pattern for data
*
* @param {BitMatrix} data
* @return {Number} Mask pattern reference number
*/
export function getBestMask(data, setupFormatFunc) {
const numPatterns = Object.keys(Patterns).length;
let bestPattern = 0;
let lowerPenalty = Infinity;
for (let p = 0; p < numPatterns; p++) {
setupFormatFunc(p);
applyMask(p, data);
// Calculate penalty
const penalty = getPenaltyN1(data)
+ getPenaltyN2(data)
+ getPenaltyN3(data)
+ getPenaltyN4(data);
// Undo previously applied mask
applyMask(p, data);
if (penalty < lowerPenalty) {
lowerPenalty = penalty;
bestPattern = p;
}
}
return bestPattern;
}