/** * 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} */ var PenalityScores = { N1: 3, N2: 3, N3: 40, N4: 10 } /** * Find adjacent modules in row/column with the same color * and assign a penality value. * * Points: N1 + i * i is the amount by which the number of adjacent modules of the same color exceeds 5 */ function getPenalityN1 (data) { var size = data.size var points = 0 for (var row = 0; row < size; row++) { for (var col = 0; col < size; col++) { // number of consecutive modules with same color var sameCount = 0 var dark = data.get(row, col) for (var r = -1; r <= 1; r++) { if (row + r < 0 || size <= row + r) continue for (var c = -1; c <= 1; c++) { if (col + c < 0 || size <= col + c) continue if (r === 0 && c === 0) continue if (dark === data.get(row + r, col + c)) sameCount++ } } if (sameCount > 5) { points += PenalityScores.N1 + sameCount - 5 } } } return points } /** * Find 2x2 blocks with the same color and assign a penality value * * Points: N2 * (m - 1) * (n - 1) */ function getPenalityN2 (data) { var size = data.size var points = 0 for (var row = 0; row < size - 1; row++) { for (var col = 0; col < size - 1; col++) { var count = 0 if (data.get(row, col)) count++ if (data.get(row + 1, col)) count++ if (data.get(row, col + 1)) count++ if (data.get(row + 1, col + 1)) count++ if (count === 0 || count === 4) points += PenalityScores.N2 } } return points } /** * 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 */ function getPenalityN3 (data) { var size = data.size var points = 0 var row, col for (row = 0; row < size; row++) { for (col = 0; col < size - 6; col++) { if (data.get(row, col) && !data.get(row, col + 1) && data.get(row, col + 2) && data.get(row, col + 3) && data.get(row, col + 4) && !data.get(row, col + 5) && data.get(row, col + 6)) { points += PenalityScores.N3 } } } for (col = 0; col < size; col++) { for (row = 0; row < size - 6; row++) { if (data.get(row, col) && !data.get(row + 1, col) && data.get(row + 2, col) && data.get(row + 3, col) && data.get(row + 4, col) && !data.get(row + 5, col) && data.get(row + 6, col)) { points += PenalityScores.N3 } } } return points } /** * 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% */ function getPenalityN4 (data) { var darkCount = 0 var size = data.size for (var col = 0; col < size; col++) { for (var row = 0; row < size; row++) { if (data.get(row, col)) darkCount++ } } var ratio = Math.abs(100 * darkCount / size / size - 50) / 5 return ratio * PenalityScores.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) { var size = data.size for (var col = 0; col < size; col++) { for (var 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) { var numPatterns = Object.keys(exports.Patterns).length var bestPattern = 0 var lowerPenality = Infinity for (var p = 0; p < numPatterns; p++) { exports.applyMask(p, data) // Calculate penality var penality = getPenalityN1(data) + getPenalityN2(data) + getPenalityN3(data) + getPenalityN4(data) // Undo previously applied mask exports.applyMask(p, data) if (penality < lowerPenality) { lowerPenality = penality bestPattern = p } } return bestPattern }