234 lines
6 KiB
JavaScript
234 lines
6 KiB
JavaScript
/**
|
|
* Data mask pattern reference
|
|
* @type {Object}
|
|
*/
|
|
exports.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
|
|
*/
|
|
exports.isValid = 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
|
|
*/
|
|
exports.from = function from (value) {
|
|
return exports.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
|
|
*/
|
|
exports.getPenaltyN1 = function getPenaltyN1 (data) {
|
|
const size = data.size
|
|
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)
|
|
*/
|
|
exports.getPenaltyN2 = function getPenaltyN2 (data) {
|
|
const size = data.size
|
|
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
|
|
*/
|
|
exports.getPenaltyN3 = function getPenaltyN3 (data) {
|
|
const size = data.size
|
|
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%
|
|
*/
|
|
exports.getPenaltyN4 = 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 exports.Patterns.PATTERN000: return (i + j) % 2 === 0
|
|
case exports.Patterns.PATTERN001: return i % 2 === 0
|
|
case exports.Patterns.PATTERN010: return j % 3 === 0
|
|
case exports.Patterns.PATTERN011: return (i + j) % 3 === 0
|
|
case exports.Patterns.PATTERN100: return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0
|
|
case exports.Patterns.PATTERN101: return (i * j) % 2 + (i * j) % 3 === 0
|
|
case exports.Patterns.PATTERN110: return ((i * j) % 2 + (i * j) % 3) % 2 === 0
|
|
case exports.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
|
|
*/
|
|
exports.applyMask = function applyMask (pattern, data) {
|
|
const size = data.size
|
|
|
|
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
|
|
*/
|
|
exports.getBestMask = function getBestMask (data, setupFormatFunc) {
|
|
const numPatterns = Object.keys(exports.Patterns).length
|
|
let bestPattern = 0
|
|
let lowerPenalty = Infinity
|
|
|
|
for (let p = 0; p < numPatterns; p++) {
|
|
setupFormatFunc(p)
|
|
exports.applyMask(p, data)
|
|
|
|
// Calculate penalty
|
|
const penalty =
|
|
exports.getPenaltyN1(data) +
|
|
exports.getPenaltyN2(data) +
|
|
exports.getPenaltyN3(data) +
|
|
exports.getPenaltyN4(data)
|
|
|
|
// Undo previously applied mask
|
|
exports.applyMask(p, data)
|
|
|
|
if (penalty < lowerPenalty) {
|
|
lowerPenalty = penalty
|
|
bestPattern = p
|
|
}
|
|
}
|
|
|
|
return bestPattern
|
|
}
|