detector.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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. import Version from './version';
  22. import {AlignmentPatternFinder} from './alignpat';
  23. import GridSampler from './grid';
  24. import {FinderPatternFinder} from './findpat';
  25. function PerspectiveTransform(a11, a21, a31, a12, a22, a32, a13, a23, a33) {
  26. this.a11 = a11;
  27. this.a12 = a12;
  28. this.a13 = a13;
  29. this.a21 = a21;
  30. this.a22 = a22;
  31. this.a23 = a23;
  32. this.a31 = a31;
  33. this.a32 = a32;
  34. this.a33 = a33;
  35. }
  36. PerspectiveTransform.prototype.transformPoints1 = function(points) {
  37. var max = points.length;
  38. var a11 = this.a11;
  39. var a12 = this.a12;
  40. var a13 = this.a13;
  41. var a21 = this.a21;
  42. var a22 = this.a22;
  43. var a23 = this.a23;
  44. var a31 = this.a31;
  45. var a32 = this.a32;
  46. var a33 = this.a33;
  47. for (var i = 0; i < max; i += 2) {
  48. var x = points[i];
  49. var y = points[i + 1];
  50. var denominator = a13 * x + a23 * y + a33;
  51. points[i] = (a11 * x + a21 * y + a31) / denominator;
  52. points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
  53. }
  54. };
  55. PerspectiveTransform.prototype.transformPoints2 = function(xValues, yValues) {
  56. var n = xValues.length;
  57. for (var i = 0; i < n; i++) {
  58. var x = xValues[i];
  59. var y = yValues[i];
  60. var denominator = this.a13 * x + this.a23 * y + this.a33;
  61. xValues[i] = (this.a11 * x + this.a21 * y + this.a31) / denominator;
  62. yValues[i] = (this.a12 * x + this.a22 * y + this.a32) / denominator;
  63. }
  64. };
  65. PerspectiveTransform.prototype.buildAdjoint = function() {
  66. // Adjoint is the transpose of the cofactor matrix:
  67. return new PerspectiveTransform(this.a22 * this.a33 - this.a23 * this.a32, this.a23 * this.a31 - this.a21 * this.a33, this.a21 * this.a32 - this.a22 * this.a31, this.a13 * this.a32 - this.a12 * this.a33, this.a11 * this.a33 - this.a13 * this.a31, this.a12 * this.a31 - this.a11 * this.a32, this.a12 * this.a23 - this.a13 * this.a22, this.a13 * this.a21 - this.a11 * this.a23, this.a11 * this.a22 - this.a12 * this.a21);
  68. };
  69. PerspectiveTransform.prototype.times = function(other) {
  70. return new PerspectiveTransform(this.a11 * other.a11 + this.a21 * other.a12 + this.a31 * other.a13, this.a11 * other.a21 + this.a21 * other.a22 + this.a31 * other.a23, this.a11 * other.a31 + this.a21 * other.a32 + this.a31 * other.a33, this.a12 * other.a11 + this.a22 * other.a12 + this.a32 * other.a13, this.a12 * other.a21 + this.a22 * other.a22 + this.a32 * other.a23, this.a12 * other.a31 + this.a22 * other.a32 + this.a32 * other.a33, this.a13 * other.a11 + this.a23 * other.a12 + this.a33 * other.a13, this.a13 * other.a21 + this.a23 * other.a22 + this.a33 * other.a23, this.a13 * other.a31 + this.a23 * other.a32 + this.a33 * other.a33);
  71. };
  72. PerspectiveTransform.quadrilateralToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p) {
  73. var qToS = this.quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
  74. var sToQ = this.squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
  75. return sToQ.times(qToS);
  76. };
  77. PerspectiveTransform.squareToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3) {
  78. var dy2 = y3 - y2;
  79. var dy3 = y0 - y1 + y2 - y3;
  80. if (dy2 == 0.0 && dy3 == 0.0) {
  81. return new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0, 0.0, 1.0);
  82. } else {
  83. var dx1 = x1 - x2;
  84. var dx2 = x3 - x2;
  85. var dx3 = x0 - x1 + x2 - x3;
  86. var dy1 = y1 - y2;
  87. var denominator = dx1 * dy2 - dx2 * dy1;
  88. var a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
  89. var a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
  90. return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1.0);
  91. }
  92. };
  93. PerspectiveTransform.quadrilateralToSquare = function(x0, y0, x1, y1, x2, y2, x3, y3) {
  94. // Here, the adjoint serves as the inverse:
  95. return this.squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
  96. };
  97. function DetectorResult(bits, points) {
  98. this.bits = bits;
  99. this.points = points;
  100. }
  101. export default function Detector(image) {
  102. this.image = image;
  103. this.resultPointCallback = null;
  104. }
  105. Detector.prototype.sizeOfBlackWhiteBlackRun = function(fromX, fromY, toX, toY) {
  106. // Mild variant of Bresenham's algorithm;
  107. // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
  108. var steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
  109. if (steep) {
  110. var temp = fromX;
  111. fromX = fromY;
  112. fromY = temp;
  113. temp = toX;
  114. toX = toY;
  115. toY = temp;
  116. }
  117. var dx = Math.abs(toX - fromX);
  118. var dy = Math.abs(toY - fromY);
  119. var error = -dx >> 1;
  120. var ystep = fromY < toY ? 1 : -1;
  121. var xstep = fromX < toX ? 1 : -1;
  122. var state = 0; // In black pixels, looking for white, first or second time
  123. for (var x = fromX, y = fromY; x != toX; x += xstep) {
  124. var realX = steep ? y : x;
  125. var realY = steep ? x : y;
  126. if (state == 1) {
  127. // In white pixels, looking for black
  128. if (this.image.data[realX + realY * this.image.width]) {
  129. state++;
  130. }
  131. } else {
  132. if (!this.image.data[realX + realY * this.image.width]) {
  133. state++;
  134. }
  135. }
  136. if (state == 3) {
  137. // Found black, white, black, and stumbled back onto white; done
  138. var diffX = x - fromX;
  139. var diffY = y - fromY;
  140. return Math.sqrt((diffX * diffX + diffY * diffY));
  141. }
  142. error += dy;
  143. if (error > 0) {
  144. if (y == toY) {
  145. break;
  146. }
  147. y += ystep;
  148. error -= dx;
  149. }
  150. }
  151. var diffX2 = toX - fromX;
  152. var diffY2 = toY - fromY;
  153. return Math.sqrt((diffX2 * diffX2 + diffY2 * diffY2));
  154. };
  155. Detector.prototype.sizeOfBlackWhiteBlackRunBothWays = function(fromX, fromY, toX, toY) {
  156. var result = this.sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
  157. // Now count other way -- don't run off image though of course
  158. var scale = 1.0;
  159. var otherToX = fromX - (toX - fromX);
  160. if (otherToX < 0) {
  161. scale = fromX / (fromX - otherToX);
  162. otherToX = 0;
  163. } else if (otherToX >= this.image.width) {
  164. scale = (this.image.width - 1 - fromX) / (otherToX - fromX);
  165. otherToX = this.image.width - 1;
  166. }
  167. var otherToY = Math.floor(fromY - (toY - fromY) * scale);
  168. scale = 1.0;
  169. if (otherToY < 0) {
  170. scale = fromY / (fromY - otherToY);
  171. otherToY = 0;
  172. } else if (otherToY >= this.image.height) {
  173. scale = (this.image.height - 1 - fromY) / (otherToY - fromY);
  174. otherToY = this.image.height - 1;
  175. }
  176. otherToX = Math.floor(fromX + (otherToX - fromX) * scale);
  177. result += this.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
  178. return result - 1.0; // -1 because we counted the middle pixel twice
  179. };
  180. Detector.prototype.calculateModuleSizeOneWay = function(pattern, otherPattern) {
  181. var moduleSizeEst1 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(pattern.X), Math.floor(pattern.Y), Math.floor(otherPattern.X), Math.floor(otherPattern.Y));
  182. var moduleSizeEst2 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(otherPattern.X), Math.floor(otherPattern.Y), Math.floor(pattern.X), Math.floor(pattern.Y));
  183. if (isNaN(moduleSizeEst1)) {
  184. return moduleSizeEst2 / 7.0;
  185. }
  186. if (isNaN(moduleSizeEst2)) {
  187. return moduleSizeEst1 / 7.0;
  188. }
  189. // Average them, and divide by 7 since we've counted the width of 3 black modules,
  190. // and 1 white and 1 black module on either side. Ergo, divide sum by 14.
  191. return (moduleSizeEst1 + moduleSizeEst2) / 14.0;
  192. };
  193. Detector.prototype.calculateModuleSize = function(topLeft, topRight, bottomLeft) {
  194. // Take the average
  195. return (this.calculateModuleSizeOneWay(topLeft, topRight) + this.calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0;
  196. };
  197. Detector.prototype.distance = function(pattern1, pattern2) {
  198. var xDiff = pattern1.X - pattern2.X;
  199. var yDiff = pattern1.Y - pattern2.Y;
  200. return Math.sqrt((xDiff * xDiff + yDiff * yDiff));
  201. };
  202. Detector.prototype.computeDimension = function(topLeft, topRight, bottomLeft, moduleSize) {
  203. var tltrCentersDimension = Math.round(this.distance(topLeft, topRight) / moduleSize);
  204. var tlblCentersDimension = Math.round(this.distance(topLeft, bottomLeft) / moduleSize);
  205. var dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7;
  206. switch (dimension & 0x03) {
  207. // mod 4
  208. case 0:
  209. dimension++;
  210. break;
  211. // 1? do nothing
  212. case 2:
  213. dimension--;
  214. break;
  215. case 3:
  216. throw "Error";
  217. }
  218. return dimension;
  219. };
  220. Detector.prototype.findAlignmentInRegion = function(overallEstModuleSize, estAlignmentX, estAlignmentY, allowanceFactor) {
  221. // Look for an alignment pattern (3 modules in size) around where it
  222. // should be
  223. var allowance = Math.floor(allowanceFactor * overallEstModuleSize);
  224. var alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
  225. var alignmentAreaRightX = Math.min(this.image.width - 1, estAlignmentX + allowance);
  226. if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
  227. throw "Error";
  228. }
  229. var alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
  230. var alignmentAreaBottomY = Math.min(this.image.height - 1, estAlignmentY + allowance);
  231. var alignmentFinder = new AlignmentPatternFinder(this.image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, this.resultPointCallback);
  232. return alignmentFinder.find();
  233. };
  234. Detector.prototype.createTransform = function(topLeft, topRight, bottomLeft, alignmentPattern, dimension) {
  235. var dimMinusThree = dimension - 3.5;
  236. var bottomRightX;
  237. var bottomRightY;
  238. var sourceBottomRightX;
  239. var sourceBottomRightY;
  240. if (alignmentPattern != null) {
  241. bottomRightX = alignmentPattern.X;
  242. bottomRightY = alignmentPattern.Y;
  243. sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3.0;
  244. } else {
  245. // Don't have an alignment pattern, just make up the bottom-right point
  246. bottomRightX = (topRight.X - topLeft.X) + bottomLeft.X;
  247. bottomRightY = (topRight.Y - topLeft.Y) + bottomLeft.Y;
  248. sourceBottomRightX = sourceBottomRightY = dimMinusThree;
  249. }
  250. var transform = PerspectiveTransform.quadrilateralToQuadrilateral(3.5, 3.5, dimMinusThree, 3.5, sourceBottomRightX, sourceBottomRightY, 3.5, dimMinusThree, topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRightX, bottomRightY, bottomLeft.X, bottomLeft.Y);
  251. return transform;
  252. };
  253. Detector.prototype.sampleGrid = function(image, transform, dimension) {
  254. var sampler = GridSampler;
  255. return sampler.sampleGrid3(image, dimension, transform);
  256. };
  257. Detector.prototype.processFinderPatternInfo = function(info) {
  258. var topLeft = info.topLeft;
  259. var topRight = info.topRight;
  260. var bottomLeft = info.bottomLeft;
  261. var moduleSize = this.calculateModuleSize(topLeft, topRight, bottomLeft);
  262. if (moduleSize < 1.0) {
  263. throw "Error";
  264. }
  265. var dimension = this.computeDimension(topLeft, topRight, bottomLeft, moduleSize);
  266. var provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
  267. var modulesBetweenFPCenters = provisionalVersion.DimensionForVersion - 7;
  268. var alignmentPattern = null;
  269. // Anything above version 1 has an alignment pattern
  270. if (provisionalVersion.alignmentPatternCenters.length > 0) {
  271. // Guess where a "bottom right" finder pattern would have been
  272. var bottomRightX = topRight.X - topLeft.X + bottomLeft.X;
  273. var bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y;
  274. // Estimate that alignment pattern is closer by 3 modules
  275. // from "bottom right" to known top left location
  276. var correctionToTopLeft = 1.0 - 3.0 / modulesBetweenFPCenters;
  277. var estAlignmentX = Math.floor(topLeft.X + correctionToTopLeft * (bottomRightX - topLeft.X));
  278. var estAlignmentY = Math.floor(topLeft.Y + correctionToTopLeft * (bottomRightY - topLeft.Y));
  279. // Kind of arbitrary -- expand search radius before giving up
  280. for (var i = 4; i <= 16; i <<= 1) {
  281. //try
  282. //{
  283. alignmentPattern = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i);
  284. break;
  285. //}
  286. //catch (re)
  287. //{
  288. // try next round
  289. //}
  290. }
  291. // If we didn't find alignment pattern... well try anyway without it
  292. }
  293. var transform = this.createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
  294. var bits = this.sampleGrid(this.image, transform, dimension);
  295. var points;
  296. if (alignmentPattern == null) {
  297. points = [bottomLeft, topLeft, topRight];
  298. } else {
  299. points = [bottomLeft, topLeft, topRight, alignmentPattern];
  300. }
  301. return new DetectorResult(bits, points);
  302. };
  303. Detector.prototype.detect = function() {
  304. var info = new FinderPatternFinder().findFinderPattern(this.image);
  305. return this.processFinderPatternInfo(info);
  306. };