235 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			6.0 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
 | |
| }
 | 
