mask-pattern.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /**
  2. * Data mask pattern reference
  3. * @type {Object}
  4. */
  5. exports.Patterns = {
  6. PATTERN000: 0,
  7. PATTERN001: 1,
  8. PATTERN010: 2,
  9. PATTERN011: 3,
  10. PATTERN100: 4,
  11. PATTERN101: 5,
  12. PATTERN110: 6,
  13. PATTERN111: 7
  14. }
  15. /**
  16. * Weighted penalty scores for the undesirable features
  17. * @type {Object}
  18. */
  19. var PenaltyScores = {
  20. N1: 3,
  21. N2: 3,
  22. N3: 40,
  23. N4: 10
  24. }
  25. /**
  26. * Check if mask pattern value is valid
  27. *
  28. * @param {Number} mask Mask pattern
  29. * @return {Boolean} true if valid, false otherwise
  30. */
  31. exports.isValid = function isValid (mask) {
  32. return mask != null && mask !== '' && !isNaN(mask) && mask >= 0 && mask <= 7
  33. }
  34. /**
  35. * Returns mask pattern from a value.
  36. * If value is not valid, returns undefined
  37. *
  38. * @param {Number|String} value Mask pattern value
  39. * @return {Number} Valid mask pattern or undefined
  40. */
  41. exports.from = function from (value) {
  42. return exports.isValid(value) ? parseInt(value, 10) : undefined
  43. }
  44. /**
  45. * Find adjacent modules in row/column with the same color
  46. * and assign a penalty value.
  47. *
  48. * Points: N1 + i
  49. * i is the amount by which the number of adjacent modules of the same color exceeds 5
  50. */
  51. exports.getPenaltyN1 = function getPenaltyN1 (data) {
  52. var size = data.size
  53. var points = 0
  54. var sameCountCol = 0
  55. var sameCountRow = 0
  56. var lastCol = null
  57. var lastRow = null
  58. for (var row = 0; row < size; row++) {
  59. sameCountCol = sameCountRow = 0
  60. lastCol = lastRow = null
  61. for (var col = 0; col < size; col++) {
  62. var module = data.get(row, col)
  63. if (module === lastCol) {
  64. sameCountCol++
  65. } else {
  66. if (sameCountCol >= 5) points += PenaltyScores.N1 + (sameCountCol - 5)
  67. lastCol = module
  68. sameCountCol = 1
  69. }
  70. module = data.get(col, row)
  71. if (module === lastRow) {
  72. sameCountRow++
  73. } else {
  74. if (sameCountRow >= 5) points += PenaltyScores.N1 + (sameCountRow - 5)
  75. lastRow = module
  76. sameCountRow = 1
  77. }
  78. }
  79. if (sameCountCol >= 5) points += PenaltyScores.N1 + (sameCountCol - 5)
  80. if (sameCountRow >= 5) points += PenaltyScores.N1 + (sameCountRow - 5)
  81. }
  82. return points
  83. }
  84. /**
  85. * Find 2x2 blocks with the same color and assign a penalty value
  86. *
  87. * Points: N2 * (m - 1) * (n - 1)
  88. */
  89. exports.getPenaltyN2 = function getPenaltyN2 (data) {
  90. var size = data.size
  91. var points = 0
  92. for (var row = 0; row < size - 1; row++) {
  93. for (var col = 0; col < size - 1; col++) {
  94. var last = data.get(row, col) +
  95. data.get(row, col + 1) +
  96. data.get(row + 1, col) +
  97. data.get(row + 1, col + 1)
  98. if (last === 4 || last === 0) points++
  99. }
  100. }
  101. return points * PenaltyScores.N2
  102. }
  103. /**
  104. * Find 1:1:3:1:1 ratio (dark:light:dark:light:dark) pattern in row/column,
  105. * preceded or followed by light area 4 modules wide
  106. *
  107. * Points: N3 * number of pattern found
  108. */
  109. exports.getPenaltyN3 = function getPenaltyN3 (data) {
  110. var size = data.size
  111. var points = 0
  112. var bitsCol = 0
  113. var bitsRow = 0
  114. for (var row = 0; row < size; row++) {
  115. bitsCol = bitsRow = 0
  116. for (var col = 0; col < size; col++) {
  117. bitsCol = ((bitsCol << 1) & 0x7FF) | data.get(row, col)
  118. if (col >= 10 && (bitsCol === 0x5D0 || bitsCol === 0x05D)) points++
  119. bitsRow = ((bitsRow << 1) & 0x7FF) | data.get(col, row)
  120. if (col >= 10 && (bitsRow === 0x5D0 || bitsRow === 0x05D)) points++
  121. }
  122. }
  123. return points * PenaltyScores.N3
  124. }
  125. /**
  126. * Calculate proportion of dark modules in entire symbol
  127. *
  128. * Points: N4 * k
  129. *
  130. * k is the rating of the deviation of the proportion of dark modules
  131. * in the symbol from 50% in steps of 5%
  132. */
  133. exports.getPenaltyN4 = function getPenaltyN4 (data) {
  134. var darkCount = 0
  135. var modulesCount = data.data.length
  136. for (var i = 0; i < modulesCount; i++) darkCount += data.data[i]
  137. var k = Math.abs(Math.ceil((darkCount * 100 / modulesCount) / 5) - 10)
  138. return k * PenaltyScores.N4
  139. }
  140. /**
  141. * Return mask value at given position
  142. *
  143. * @param {Number} maskPattern Pattern reference value
  144. * @param {Number} i Row
  145. * @param {Number} j Column
  146. * @return {Boolean} Mask value
  147. */
  148. function getMaskAt (maskPattern, i, j) {
  149. switch (maskPattern) {
  150. case exports.Patterns.PATTERN000: return (i + j) % 2 === 0
  151. case exports.Patterns.PATTERN001: return i % 2 === 0
  152. case exports.Patterns.PATTERN010: return j % 3 === 0
  153. case exports.Patterns.PATTERN011: return (i + j) % 3 === 0
  154. case exports.Patterns.PATTERN100: return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0
  155. case exports.Patterns.PATTERN101: return (i * j) % 2 + (i * j) % 3 === 0
  156. case exports.Patterns.PATTERN110: return ((i * j) % 2 + (i * j) % 3) % 2 === 0
  157. case exports.Patterns.PATTERN111: return ((i * j) % 3 + (i + j) % 2) % 2 === 0
  158. default: throw new Error('bad maskPattern:' + maskPattern)
  159. }
  160. }
  161. /**
  162. * Apply a mask pattern to a BitMatrix
  163. *
  164. * @param {Number} pattern Pattern reference number
  165. * @param {BitMatrix} data BitMatrix data
  166. */
  167. exports.applyMask = function applyMask (pattern, data) {
  168. var size = data.size
  169. for (var col = 0; col < size; col++) {
  170. for (var row = 0; row < size; row++) {
  171. if (data.isReserved(row, col)) continue
  172. data.xor(row, col, getMaskAt(pattern, row, col))
  173. }
  174. }
  175. }
  176. /**
  177. * Returns the best mask pattern for data
  178. *
  179. * @param {BitMatrix} data
  180. * @return {Number} Mask pattern reference number
  181. */
  182. exports.getBestMask = function getBestMask (data, setupFormatFunc) {
  183. var numPatterns = Object.keys(exports.Patterns).length
  184. var bestPattern = 0
  185. var lowerPenalty = Infinity
  186. for (var p = 0; p < numPatterns; p++) {
  187. setupFormatFunc(p)
  188. exports.applyMask(p, data)
  189. // Calculate penalty
  190. var penalty =
  191. exports.getPenaltyN1(data) +
  192. exports.getPenaltyN2(data) +
  193. exports.getPenaltyN3(data) +
  194. exports.getPenaltyN4(data)
  195. // Undo previously applied mask
  196. exports.applyMask(p, data)
  197. if (penalty < lowerPenalty) {
  198. lowerPenalty = penalty
  199. bestPattern = p
  200. }
  201. }
  202. return bestPattern
  203. }