node-qrcode-lite/lib/core/mask-pattern.js
Vincenzo Greco 77064f5e5e Refactor/core ()
* Split core lib into multiple files

* Refactor data encoding methods

* Refactor data masking process

* Improve qr code generation process

* Increase minimum required node version to 0.10

* Add linter

* Add tests and tests coverage

* Update travis config to fix compilation issues

* Add examples folder

* Add missing license tag in package.json

* Update build script and add sourcemap support

* Publish only strictly needed files on npm

* Update readme
2016-12-16 23:45:08 +01:00

221 lines
5.4 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}
*/
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
}