findpat.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*
  2. Ported to JavaScript by Lazar Laszlo 2011
  3. lazarsoft@gmail.com, www.lazarsoft.info
  4. */
  5. /*
  6. *
  7. * Copyright 2007 ZXing authors
  8. *
  9. * Licensed under the Apache License, Version 2.0 (the "License");
  10. * you may not use this file except in compliance with the License.
  11. * You may obtain a copy of the License at
  12. *
  13. * http://www.apache.org/licenses/LICENSE-2.0
  14. *
  15. * Unless required by applicable law or agreed to in writing, software
  16. * distributed under the License is distributed on an "AS IS" BASIS,
  17. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. * See the License for the specific language governing permissions and
  19. * limitations under the License.
  20. */
  21. var MIN_SKIP = 3;
  22. var MAX_MODULES = 57;
  23. var INTEGER_MATH_SHIFT = 8;
  24. var CENTER_QUORUM = 2;
  25. function orderBestPatterns(patterns) {
  26. function distance(pattern1, pattern2) {
  27. var xDiff = pattern1.X - pattern2.X;
  28. var yDiff = pattern1.Y - pattern2.Y;
  29. return Math.sqrt((xDiff * xDiff + yDiff * yDiff));
  30. }
  31. /// <summary> Returns the z component of the cross product between vectors BC and BA.</summary>
  32. function crossProductZ(pointA, pointB, pointC) {
  33. var bX = pointB.x;
  34. var bY = pointB.y;
  35. return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
  36. }
  37. // Find distances between pattern centers
  38. var zeroOneDistance = distance(patterns[0], patterns[1]);
  39. var oneTwoDistance = distance(patterns[1], patterns[2]);
  40. var zeroTwoDistance = distance(patterns[0], patterns[2]);
  41. var pointA, pointB, pointC;
  42. // Assume one closest to other two is B; A and C will just be guesses at first
  43. if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
  44. pointB = patterns[0];
  45. pointA = patterns[1];
  46. pointC = patterns[2];
  47. } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
  48. pointB = patterns[1];
  49. pointA = patterns[0];
  50. pointC = patterns[2];
  51. } else {
  52. pointB = patterns[2];
  53. pointA = patterns[0];
  54. pointC = patterns[1];
  55. }
  56. // Use cross product to figure out whether A and C are correct or flipped.
  57. // This asks whether BC x BA has a positive z component, which is the arrangement
  58. // we want for A, B, C. If it's negative, then we've got it flipped around and
  59. // should swap A and C.
  60. if (crossProductZ(pointA, pointB, pointC) < 0.0) {
  61. var temp = pointA;
  62. pointA = pointC;
  63. pointC = temp;
  64. }
  65. patterns[0] = pointA;
  66. patterns[1] = pointB;
  67. patterns[2] = pointC;
  68. }
  69. function FinderPattern(posX, posY, estimatedModuleSize) {
  70. this.x = posX;
  71. this.y = posY;
  72. this.count = 1;
  73. this.estimatedModuleSize = estimatedModuleSize;
  74. }
  75. Object.defineProperty(FinderPattern.prototype, "X", {
  76. get: function() {
  77. return this.x;
  78. }
  79. });
  80. Object.defineProperty(FinderPattern.prototype, "Y", {
  81. get: function() {
  82. return this.y;
  83. }
  84. });
  85. FinderPattern.prototype.incrementCount = function() {
  86. this.count++;
  87. };
  88. FinderPattern.prototype.aboutEquals = function(moduleSize, i, j) {
  89. if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) {
  90. var moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize);
  91. return moduleSizeDiff <= 1.0 || moduleSizeDiff / this.estimatedModuleSize <= 1.0;
  92. }
  93. return false;
  94. };
  95. function FinderPatternInfo(patternCenters) {
  96. this.bottomLeft = patternCenters[0];
  97. this.topLeft = patternCenters[1];
  98. this.topRight = patternCenters[2];
  99. }
  100. export function FinderPatternFinder() {
  101. this.image = null;
  102. this.possibleCenters = [];
  103. this.hasSkipped = false;
  104. this.crossCheckStateCount = [0, 0, 0, 0, 0];
  105. this.resultPointCallback = null;
  106. }
  107. Object.defineProperty(FinderPatternFinder.prototype, "CrossCheckStateCount", {
  108. get: function() {
  109. this.crossCheckStateCount[0] = 0;
  110. this.crossCheckStateCount[1] = 0;
  111. this.crossCheckStateCount[2] = 0;
  112. this.crossCheckStateCount[3] = 0;
  113. this.crossCheckStateCount[4] = 0;
  114. return this.crossCheckStateCount;
  115. }
  116. });
  117. FinderPatternFinder.prototype.foundPatternCross = function(stateCount) {
  118. var totalModuleSize = 0;
  119. for (var i = 0; i < 5; i++) {
  120. var count = stateCount[i];
  121. if (count == 0) {
  122. return false;
  123. }
  124. totalModuleSize += count;
  125. }
  126. if (totalModuleSize < 7) {
  127. return false;
  128. }
  129. var moduleSize = Math.floor((totalModuleSize << INTEGER_MATH_SHIFT) / 7);
  130. var maxVariance = Math.floor(moduleSize / 2);
  131. // Allow less than 50% variance from 1-1-3-1-1 proportions
  132. return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance && Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
  133. };
  134. FinderPatternFinder.prototype.centerFromEnd = function(stateCount, end) {
  135. return (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0;
  136. };
  137. FinderPatternFinder.prototype.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) {
  138. var image = this.image;
  139. var maxI = image.height;
  140. var stateCount = this.CrossCheckStateCount;
  141. // Start counting up from center
  142. var i = startI;
  143. while (i >= 0 && image.data[centerJ + i * image.width]) {
  144. stateCount[2]++;
  145. i--;
  146. }
  147. if (i < 0) {
  148. return NaN;
  149. }
  150. while (i >= 0 && !image.data[centerJ + i * image.width] && stateCount[1] <= maxCount) {
  151. stateCount[1]++;
  152. i--;
  153. }
  154. // If already too many modules in this state or ran off the edge:
  155. if (i < 0 || stateCount[1] > maxCount) {
  156. return NaN;
  157. }
  158. while (i >= 0 && image.data[centerJ + i * image.width] && stateCount[0] <= maxCount) {
  159. stateCount[0]++;
  160. i--;
  161. }
  162. if (stateCount[0] > maxCount) {
  163. return NaN;
  164. }
  165. // Now also count down from center
  166. i = startI + 1;
  167. while (i < maxI && image.data[centerJ + i * image.width]) {
  168. stateCount[2]++;
  169. i++;
  170. }
  171. if (i == maxI) {
  172. return NaN;
  173. }
  174. while (i < maxI && !image.data[centerJ + i * image.width] && stateCount[3] < maxCount) {
  175. stateCount[3]++;
  176. i++;
  177. }
  178. if (i == maxI || stateCount[3] >= maxCount) {
  179. return NaN;
  180. }
  181. while (i < maxI && image.data[centerJ + i * image.width] && stateCount[4] < maxCount) {
  182. stateCount[4]++;
  183. i++;
  184. }
  185. if (stateCount[4] >= maxCount) {
  186. return NaN;
  187. }
  188. // If we found a finder-pattern-like section, but its size is more than 40% different than
  189. // the original, assume it's a false positive
  190. var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
  191. if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
  192. return NaN;
  193. }
  194. return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN;
  195. };
  196. FinderPatternFinder.prototype.crossCheckHorizontal = function(startJ, centerI, maxCount, originalStateCountTotal) {
  197. var image = this.image;
  198. var maxJ = image.width;
  199. var stateCount = this.CrossCheckStateCount;
  200. var j = startJ;
  201. while (j >= 0 && image.data[j + centerI * image.width]) {
  202. stateCount[2]++;
  203. j--;
  204. }
  205. if (j < 0) {
  206. return NaN;
  207. }
  208. while (j >= 0 && !image.data[j + centerI * image.width] && stateCount[1] <= maxCount) {
  209. stateCount[1]++;
  210. j--;
  211. }
  212. if (j < 0 || stateCount[1] > maxCount) {
  213. return NaN;
  214. }
  215. while (j >= 0 && image.data[j + centerI * image.width] && stateCount[0] <= maxCount) {
  216. stateCount[0]++;
  217. j--;
  218. }
  219. if (stateCount[0] > maxCount) {
  220. return NaN;
  221. }
  222. j = startJ + 1;
  223. while (j < maxJ && image.data[j + centerI * image.width]) {
  224. stateCount[2]++;
  225. j++;
  226. }
  227. if (j == maxJ) {
  228. return NaN;
  229. }
  230. while (j < maxJ && !image.data[j + centerI * image.width] && stateCount[3] < maxCount) {
  231. stateCount[3]++;
  232. j++;
  233. }
  234. if (j == maxJ || stateCount[3] >= maxCount) {
  235. return NaN;
  236. }
  237. while (j < maxJ && image.data[j + centerI * image.width] && stateCount[4] < maxCount) {
  238. stateCount[4]++;
  239. j++;
  240. }
  241. if (stateCount[4] >= maxCount) {
  242. return NaN;
  243. }
  244. // If we found a finder-pattern-like section, but its size is significantly different than
  245. // the original, assume it's a false positive
  246. var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
  247. if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {
  248. return NaN;
  249. }
  250. return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, j) : NaN;
  251. };
  252. FinderPatternFinder.prototype.handlePossibleCenter = function(stateCount, i, j) {
  253. var stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
  254. var centerJ = this.centerFromEnd(stateCount, j); //float
  255. var centerI = this.crossCheckVertical(i, Math.floor(centerJ), stateCount[2], stateCountTotal); //float
  256. if (!isNaN(centerI)) {
  257. // Re-cross check
  258. centerJ = this.crossCheckHorizontal(Math.floor(centerJ), Math.floor(centerI), stateCount[2], stateCountTotal);
  259. if (!isNaN(centerJ)) {
  260. var estimatedModuleSize = stateCountTotal / 7.0;
  261. var found = false;
  262. var max = this.possibleCenters.length;
  263. for (var index = 0; index < max; index++) {
  264. var center = this.possibleCenters[index];
  265. // Look for about the same center and module size:
  266. if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
  267. center.incrementCount();
  268. found = true;
  269. break;
  270. }
  271. }
  272. if (!found) {
  273. var point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
  274. this.possibleCenters.push(point);
  275. if (this.resultPointCallback != null) {
  276. this.resultPointCallback.foundPossibleResultPoint(point);
  277. }
  278. }
  279. return true;
  280. }
  281. }
  282. return false;
  283. };
  284. FinderPatternFinder.prototype.selectBestPatterns = function() {
  285. var startSize = this.possibleCenters.length;
  286. if (startSize < 3) {
  287. // Couldn't find enough finder patterns
  288. throw "Couldn't find enough finder patterns:" + startSize + " patterns found";
  289. }
  290. // Filter outlier possibilities whose module size is too different
  291. if (startSize > 3) {
  292. // But we can only afford to do so if we have at least 4 possibilities to choose from
  293. var totalModuleSize = 0.0;
  294. var square = 0.0;
  295. for (var i = 0; i < startSize; i++) {
  296. var centerValue = this.possibleCenters[i].estimatedModuleSize;
  297. totalModuleSize += centerValue;
  298. square += (centerValue * centerValue);
  299. }
  300. var average = totalModuleSize / startSize;
  301. this.possibleCenters.sort(function(center1, center2) {
  302. var dA = Math.abs(center2.estimatedModuleSize - average);
  303. var dB = Math.abs(center1.estimatedModuleSize - average);
  304. if (dA < dB) {
  305. return (-1);
  306. } else if (dA == dB) {
  307. return 0;
  308. } else {
  309. return 1;
  310. }
  311. });
  312. var stdDev = Math.sqrt(square / startSize - average * average);
  313. var limit = Math.max(0.2 * average, stdDev);
  314. for (var i = this.possibleCenters - 1; i >= 0; i--) {
  315. var pattern = this.possibleCenters[i];
  316. if (Math.abs(pattern.estimatedModuleSize - average) > limit) {
  317. this.possibleCenters.splice(i, 1);
  318. }
  319. }
  320. }
  321. if (this.possibleCenters.length > 3) {
  322. // Throw away all but those first size candidate points we found.
  323. this.possibleCenters.sort(function(a, b) {
  324. if (a.count > b.count) return -1;
  325. if (a.count < b.count) return 1;
  326. return 0;
  327. });
  328. }
  329. return [this.possibleCenters[0], this.possibleCenters[1], this.possibleCenters[2]];
  330. };
  331. FinderPatternFinder.prototype.findRowSkip = function() {
  332. var max = this.possibleCenters.length;
  333. if (max <= 1) {
  334. return 0;
  335. }
  336. var firstConfirmedCenter = null;
  337. for (var i = 0; i < max; i++) {
  338. var center = this.possibleCenters[i];
  339. if (center.count >= CENTER_QUORUM) {
  340. if (firstConfirmedCenter == null) {
  341. firstConfirmedCenter = center;
  342. } else {
  343. // We have two confirmed centers
  344. // How far down can we skip before resuming looking for the next
  345. // pattern? In the worst case, only the difference between the
  346. // difference in the x / y coordinates of the two centers.
  347. // This is the case where you find top left last.
  348. this.hasSkipped = true;
  349. return Math.floor((Math.abs(firstConfirmedCenter.X - center.X) - Math.abs(firstConfirmedCenter.Y - center.Y)) / 2);
  350. }
  351. }
  352. }
  353. return 0;
  354. };
  355. FinderPatternFinder.prototype.haveMultiplyConfirmedCenters = function() {
  356. var confirmedCount = 0;
  357. var totalModuleSize = 0.0;
  358. var max = this.possibleCenters.length;
  359. for (var i = 0; i < max; i++) {
  360. var pattern = this.possibleCenters[i];
  361. if (pattern.count >= CENTER_QUORUM) {
  362. confirmedCount++;
  363. totalModuleSize += pattern.estimatedModuleSize;
  364. }
  365. }
  366. if (confirmedCount < 3) {
  367. return false;
  368. }
  369. // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
  370. // and that we need to keep looking. We detect this by asking if the estimated module sizes
  371. // vary too much. We arbitrarily say that when the total deviation from average exceeds
  372. // 5% of the total module size estimates, it's too much.
  373. var average = totalModuleSize / max;
  374. var totalDeviation = 0.0;
  375. for (var i = 0; i < max; i++) {
  376. pattern = this.possibleCenters[i];
  377. totalDeviation += Math.abs(pattern.estimatedModuleSize - average);
  378. }
  379. return totalDeviation <= 0.05 * totalModuleSize;
  380. };
  381. FinderPatternFinder.prototype.findFinderPattern = function(image) {
  382. var tryHarder = false;
  383. this.image = image;
  384. var maxI = image.height;
  385. var maxJ = image.width;
  386. var iSkip = Math.floor((3 * maxI) / (4 * MAX_MODULES));
  387. if (iSkip < MIN_SKIP || tryHarder) {
  388. iSkip = MIN_SKIP;
  389. }
  390. var done = false;
  391. var stateCount = new Array(5);
  392. for (var i = iSkip - 1; i < maxI && !done; i += iSkip) {
  393. // Get a row of black/white values
  394. stateCount[0] = 0;
  395. stateCount[1] = 0;
  396. stateCount[2] = 0;
  397. stateCount[3] = 0;
  398. stateCount[4] = 0;
  399. var currentState = 0;
  400. for (var j = 0; j < maxJ; j++) {
  401. if (image.data[j + i * image.width]) {
  402. // Black pixel
  403. if ((currentState & 1) == 1) {
  404. // Counting white pixels
  405. currentState++;
  406. }
  407. stateCount[currentState]++;
  408. } else {
  409. // White pixel
  410. if ((currentState & 1) == 0) {
  411. // Counting black pixels
  412. if (currentState == 4) {
  413. // A winner?
  414. if (this.foundPatternCross(stateCount)) {
  415. // Yes
  416. var confirmed = this.handlePossibleCenter(stateCount, i, j);
  417. if (confirmed) {
  418. // Start examining every other line. Checking each line turned out to be too
  419. // expensive and didn't improve performance.
  420. iSkip = 2;
  421. if (this.hasSkipped) {
  422. done = this.haveMultiplyConfirmedCenters();
  423. } else {
  424. var rowSkip = this.findRowSkip();
  425. if (rowSkip > stateCount[2]) {
  426. // Skip rows between row of lower confirmed center
  427. // and top of presumed third confirmed center
  428. // but back up a bit to get a full chance of detecting
  429. // it, entire width of center of finder pattern
  430. // Skip by rowSkip, but back off by stateCount[2] (size of last center
  431. // of pattern we saw) to be conservative, and also back off by iSkip which
  432. // is about to be re-added
  433. i += rowSkip - stateCount[2] - iSkip;
  434. j = maxJ - 1;
  435. }
  436. }
  437. } else {
  438. // Advance to next black pixel
  439. do {
  440. j++;
  441. } while (j < maxJ && !image.data[j + i * image.width]);
  442. j--; // back up to that last white pixel
  443. }
  444. // Clear state to start looking again
  445. currentState = 0;
  446. stateCount[0] = 0;
  447. stateCount[1] = 0;
  448. stateCount[2] = 0;
  449. stateCount[3] = 0;
  450. stateCount[4] = 0;
  451. } else {
  452. // No, shift counts back by two
  453. stateCount[0] = stateCount[2];
  454. stateCount[1] = stateCount[3];
  455. stateCount[2] = stateCount[4];
  456. stateCount[3] = 1;
  457. stateCount[4] = 0;
  458. currentState = 3;
  459. }
  460. } else {
  461. stateCount[++currentState]++;
  462. }
  463. } else {
  464. // Counting white pixels
  465. stateCount[currentState]++;
  466. }
  467. }
  468. }
  469. if (this.foundPatternCross(stateCount)) {
  470. var confirmed = this.handlePossibleCenter(stateCount, i, maxJ);
  471. if (confirmed) {
  472. iSkip = stateCount[0];
  473. if (this.hasSkipped) {
  474. // Found a third one
  475. done = this.haveMultiplyConfirmedCenters();
  476. }
  477. }
  478. }
  479. }
  480. var patternInfo = this.selectBestPatterns();
  481. orderBestPatterns(patternInfo);
  482. return new FinderPatternInfo(patternInfo);
  483. };