/** * 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 }