You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ZXQRCodeDetector.m 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. * Copyright 2012 ZXing authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "ZXBitMatrix.h"
  17. #import "ZXDecodeHints.h"
  18. #import "ZXDetectorResult.h"
  19. #import "ZXErrors.h"
  20. #import "ZXGridSampler.h"
  21. #import "ZXIntArray.h"
  22. #import "ZXMathUtils.h"
  23. #import "ZXPerspectiveTransform.h"
  24. #import "ZXQRCodeAlignmentPattern.h"
  25. #import "ZXQRCodeAlignmentPatternFinder.h"
  26. #import "ZXQRCodeDetector.h"
  27. #import "ZXQRCodeFinderPattern.h"
  28. #import "ZXQRCodeFinderPatternFinder.h"
  29. #import "ZXQRCodeFinderPatternInfo.h"
  30. #import "ZXQRCodeVersion.h"
  31. #import "ZXResultPoint.h"
  32. #import "ZXResultPointCallback.h"
  33. @interface ZXQRCodeDetector ()
  34. @property (nonatomic, weak) id<ZXResultPointCallback> resultPointCallback;
  35. @end
  36. @implementation ZXQRCodeDetector
  37. - (id)initWithImage:(ZXBitMatrix *)image {
  38. if (self = [super init]) {
  39. _image = image;
  40. }
  41. return self;
  42. }
  43. - (ZXDetectorResult *)detectWithError:(NSError **)error {
  44. return [self detect:nil error:error];
  45. }
  46. - (ZXDetectorResult *)detect:(ZXDecodeHints *)hints error:(NSError **)error {
  47. self.resultPointCallback = hints == nil ? nil : hints.resultPointCallback;
  48. ZXQRCodeFinderPatternFinder *finder = [[ZXQRCodeFinderPatternFinder alloc] initWithImage:self.image resultPointCallback:self.resultPointCallback];
  49. ZXQRCodeFinderPatternInfo *info = [finder find:hints error:error];
  50. if (!info) {
  51. return nil;
  52. }
  53. return [self processFinderPatternInfo:info error:error];
  54. }
  55. - (ZXDetectorResult *)processFinderPatternInfo:(ZXQRCodeFinderPatternInfo *)info error:(NSError **)error {
  56. ZXQRCodeFinderPattern *topLeft = info.topLeft;
  57. ZXQRCodeFinderPattern *topRight = info.topRight;
  58. ZXQRCodeFinderPattern *bottomLeft = info.bottomLeft;
  59. float moduleSize = [self calculateModuleSize:topLeft topRight:topRight bottomLeft:bottomLeft];
  60. if (moduleSize < 1.0f) {
  61. if (error) *error = ZXNotFoundErrorInstance();
  62. return nil;
  63. }
  64. int dimension = [ZXQRCodeDetector computeDimension:topLeft topRight:topRight bottomLeft:bottomLeft moduleSize:moduleSize error:error];
  65. if (dimension == -1) {
  66. return nil;
  67. }
  68. ZXQRCodeVersion *provisionalVersion = [ZXQRCodeVersion provisionalVersionForDimension:dimension];
  69. if (!provisionalVersion) {
  70. if (error) *error = ZXFormatErrorInstance();
  71. return nil;
  72. }
  73. int modulesBetweenFPCenters = [provisionalVersion dimensionForVersion] - 7;
  74. ZXQRCodeAlignmentPattern *alignmentPattern = nil;
  75. if (provisionalVersion.alignmentPatternCenters.length > 0) {
  76. float bottomRightX = [topRight x] - [topLeft x] + [bottomLeft x];
  77. float bottomRightY = [topRight y] - [topLeft y] + [bottomLeft y];
  78. float correctionToTopLeft = 1.0f - 3.0f / (float)modulesBetweenFPCenters;
  79. int estAlignmentX = (int)([topLeft x] + correctionToTopLeft * (bottomRightX - [topLeft x]));
  80. int estAlignmentY = (int)([topLeft y] + correctionToTopLeft * (bottomRightY - [topLeft y]));
  81. for (int i = 4; i <= 16; i <<= 1) {
  82. NSError *alignmentError = nil;
  83. alignmentPattern = [self findAlignmentInRegion:moduleSize estAlignmentX:estAlignmentX estAlignmentY:estAlignmentY allowanceFactor:(float)i error:&alignmentError];
  84. if (alignmentPattern) {
  85. break;
  86. } else if (alignmentError.code != ZXNotFoundError) {
  87. if (error) *error = alignmentError;
  88. return nil;
  89. }
  90. }
  91. }
  92. ZXPerspectiveTransform *transform = [ZXQRCodeDetector createTransform:topLeft topRight:topRight bottomLeft:bottomLeft alignmentPattern:alignmentPattern dimension:dimension];
  93. ZXBitMatrix *bits = [self sampleGrid:self.image transform:transform dimension:dimension error:error];
  94. if (!bits) {
  95. return nil;
  96. }
  97. NSArray *points;
  98. if (alignmentPattern == nil) {
  99. points = @[bottomLeft, topLeft, topRight];
  100. } else {
  101. points = @[bottomLeft, topLeft, topRight, alignmentPattern];
  102. }
  103. return [[ZXDetectorResult alloc] initWithBits:bits points:points];
  104. }
  105. + (ZXPerspectiveTransform *)createTransform:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight bottomLeft:(ZXResultPoint *)bottomLeft alignmentPattern:(ZXResultPoint *)alignmentPattern dimension:(int)dimension {
  106. float dimMinusThree = (float)dimension - 3.5f;
  107. float bottomRightX;
  108. float bottomRightY;
  109. float sourceBottomRightX;
  110. float sourceBottomRightY;
  111. if (alignmentPattern != nil) {
  112. bottomRightX = alignmentPattern.x;
  113. bottomRightY = alignmentPattern.y;
  114. sourceBottomRightX = dimMinusThree - 3.0f;
  115. sourceBottomRightY = sourceBottomRightX;
  116. } else {
  117. bottomRightX = (topRight.x - topLeft.x) + bottomLeft.x;
  118. bottomRightY = (topRight.y - topLeft.y) + bottomLeft.y;
  119. sourceBottomRightX = dimMinusThree;
  120. sourceBottomRightY = dimMinusThree;
  121. }
  122. return [ZXPerspectiveTransform quadrilateralToQuadrilateral:3.5f y0:3.5f
  123. x1:dimMinusThree y1:3.5f
  124. x2:sourceBottomRightX y2:sourceBottomRightY
  125. x3:3.5f y3:dimMinusThree
  126. x0p:topLeft.x y0p:topLeft.y
  127. x1p:topRight.x y1p:topRight.y
  128. x2p:bottomRightX y2p:bottomRightY
  129. x3p:bottomLeft.x y3p:bottomLeft.y];
  130. }
  131. - (ZXBitMatrix *)sampleGrid:(ZXBitMatrix *)anImage transform:(ZXPerspectiveTransform *)transform dimension:(int)dimension error:(NSError **)error {
  132. ZXGridSampler *sampler = [ZXGridSampler instance];
  133. return [sampler sampleGrid:anImage dimensionX:dimension dimensionY:dimension transform:transform error:error];
  134. }
  135. /**
  136. * Computes the dimension (number of modules on a size) of the QR Code based on the position
  137. * of the finder patterns and estimated module size. Returns -1 on an error.
  138. */
  139. + (int)computeDimension:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight bottomLeft:(ZXResultPoint *)bottomLeft moduleSize:(float)moduleSize error:(NSError **)error {
  140. int tltrCentersDimension = [ZXMathUtils round:[ZXResultPoint distance:topLeft pattern2:topRight] / moduleSize];
  141. int tlblCentersDimension = [ZXMathUtils round:[ZXResultPoint distance:topLeft pattern2:bottomLeft] / moduleSize];
  142. int dimension = ((tltrCentersDimension + tlblCentersDimension) / 2) + 7;
  143. switch (dimension & 0x03) {
  144. case 0:
  145. dimension++;
  146. break;
  147. case 2:
  148. dimension--;
  149. break;
  150. case 3:
  151. if (error) *error = ZXNotFoundErrorInstance();
  152. return -1;
  153. }
  154. return dimension;
  155. }
  156. /**
  157. * Computes an average estimated module size based on estimated derived from the positions
  158. * of the three finder patterns.
  159. */
  160. - (float)calculateModuleSize:(ZXResultPoint *)topLeft topRight:(ZXResultPoint *)topRight bottomLeft:(ZXResultPoint *)bottomLeft {
  161. return ([self calculateModuleSizeOneWay:topLeft otherPattern:topRight] + [self calculateModuleSizeOneWay:topLeft otherPattern:bottomLeft]) / 2.0f;
  162. }
  163. /**
  164. * Estimates module size based on two finder patterns -- it uses
  165. * sizeOfBlackWhiteBlackRunBothWays:fromY:toX:toY: to figure the
  166. * width of each, measuring along the axis between their centers.
  167. */
  168. - (float)calculateModuleSizeOneWay:(ZXResultPoint *)pattern otherPattern:(ZXResultPoint *)otherPattern {
  169. float moduleSizeEst1 = [self sizeOfBlackWhiteBlackRunBothWays:(int)[pattern x] fromY:(int)[pattern y] toX:(int)[otherPattern x] toY:(int)[otherPattern y]];
  170. float moduleSizeEst2 = [self sizeOfBlackWhiteBlackRunBothWays:(int)[otherPattern x] fromY:(int)[otherPattern y] toX:(int)[pattern x] toY:(int)[pattern y]];
  171. if (isnan(moduleSizeEst1)) {
  172. return moduleSizeEst2 / 7.0f;
  173. }
  174. if (isnan(moduleSizeEst2)) {
  175. return moduleSizeEst1 / 7.0f;
  176. }
  177. return (moduleSizeEst1 + moduleSizeEst2) / 14.0f;
  178. }
  179. /**
  180. * See sizeOfBlackWhiteBlackRun:fromY:toX:toY: <p>computes the total width of
  181. * a finder pattern by looking for a black-white-black run from the center in the direction
  182. * of another point (another finder pattern center), and in the opposite direction too.</p>
  183. */
  184. - (float)sizeOfBlackWhiteBlackRunBothWays:(int)fromX fromY:(int)fromY toX:(int)toX toY:(int)toY {
  185. float result = [self sizeOfBlackWhiteBlackRun:fromX fromY:fromY toX:toX toY:toY];
  186. // Now count other way -- don't run off image though of course
  187. float scale = 1.0f;
  188. int otherToX = fromX - (toX - fromX);
  189. if (otherToX < 0) {
  190. scale = (float)fromX / (float)(fromX - otherToX);
  191. otherToX = 0;
  192. } else if (otherToX >= self.image.width) {
  193. scale = (float)(self.image.width - 1 - fromX) / (float)(otherToX - fromX);
  194. otherToX = self.image.width - 1;
  195. }
  196. int otherToY = (int)(fromY - (toY - fromY) * scale);
  197. scale = 1.0f;
  198. if (otherToY < 0) {
  199. scale = (float)fromY / (float)(fromY - otherToY);
  200. otherToY = 0;
  201. } else if (otherToY >= self.image.height) {
  202. scale = (float)(self.image.height - 1 - fromY) / (float)(otherToY - fromY);
  203. otherToY = self.image.height - 1;
  204. }
  205. otherToX = (int)(fromX + (otherToX - fromX) * scale);
  206. result += [self sizeOfBlackWhiteBlackRun:fromX fromY:fromY toX:otherToX toY:otherToY];
  207. // Middle pixel is double-counted this way; subtract 1
  208. return result - 1.0f;
  209. }
  210. /**
  211. * This method traces a line from a point in the image, in the direction towards another point.
  212. * It begins in a black region, and keeps going until it finds white, then black, then white again.
  213. * It reports the distance from the start to this point.
  214. *
  215. * This is used when figuring out how wide a finder pattern is, when the finder pattern
  216. * may be skewed or rotated.
  217. */
  218. - (float)sizeOfBlackWhiteBlackRun:(int)fromX fromY:(int)fromY toX:(int)toX toY:(int)toY {
  219. // Mild variant of Bresenham's algorithm;
  220. // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
  221. BOOL steep = abs(toY - fromY) > abs(toX - fromX);
  222. if (steep) {
  223. int temp = fromX;
  224. fromX = fromY;
  225. fromY = temp;
  226. temp = toX;
  227. toX = toY;
  228. toY = temp;
  229. }
  230. int dx = abs(toX - fromX);
  231. int dy = abs(toY - fromY);
  232. int error = -dx / 2;
  233. int xstep = fromX < toX ? 1 : -1;
  234. int ystep = fromY < toY ? 1 : -1;
  235. // In black pixels, looking for white, first or second time.
  236. int state = 0;
  237. // Loop up until x == toX, but not beyond
  238. int xLimit = toX + xstep;
  239. for (int x = fromX, y = fromY; x != xLimit; x += xstep) {
  240. int realX = steep ? y : x;
  241. int realY = steep ? x : y;
  242. // Does current pixel mean we have moved white to black or vice versa?
  243. // Scanning black in state 0,2 and white in state 1, so if we find the wrong
  244. // color, advance to next state or end if we are in state 2 already
  245. if ((state == 1) == [self.image getX:realX y:realY]) {
  246. if (state == 2) {
  247. return [ZXMathUtils distanceInt:x aY:y bX:fromX bY:fromY];
  248. }
  249. state++;
  250. }
  251. error += dy;
  252. if (error > 0) {
  253. if (y == toY) {
  254. break;
  255. }
  256. y += ystep;
  257. error -= dx;
  258. }
  259. }
  260. // Found black-white-black; give the benefit of the doubt that the next pixel outside the image
  261. // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a
  262. // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
  263. if (state == 2) {
  264. return [ZXMathUtils distanceInt:toX + xstep aY:toY bX:fromX bY:fromY];
  265. }
  266. // else we didn't find even black-white-black; no estimate is really possible
  267. return NAN;
  268. }
  269. - (ZXQRCodeAlignmentPattern *)findAlignmentInRegion:(float)overallEstModuleSize estAlignmentX:(int)estAlignmentX estAlignmentY:(int)estAlignmentY allowanceFactor:(float)allowanceFactor error:(NSError **)error {
  270. int allowance = (int)(allowanceFactor * overallEstModuleSize);
  271. int alignmentAreaLeftX = MAX(0, estAlignmentX - allowance);
  272. int alignmentAreaRightX = MIN(self.image.width - 1, estAlignmentX + allowance);
  273. if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
  274. if (error) *error = ZXNotFoundErrorInstance();
  275. return nil;
  276. }
  277. int alignmentAreaTopY = MAX(0, estAlignmentY - allowance);
  278. int alignmentAreaBottomY = MIN(self.image.height - 1, estAlignmentY + allowance);
  279. if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) {
  280. if (error) *error = ZXNotFoundErrorInstance();
  281. return nil;
  282. }
  283. ZXQRCodeAlignmentPatternFinder *alignmentFinder = [[ZXQRCodeAlignmentPatternFinder alloc] initWithImage:self.image
  284. startX:alignmentAreaLeftX
  285. startY:alignmentAreaTopY
  286. width:alignmentAreaRightX - alignmentAreaLeftX
  287. height:alignmentAreaBottomY - alignmentAreaTopY
  288. moduleSize:overallEstModuleSize
  289. resultPointCallback:self.resultPointCallback];
  290. return [alignmentFinder findWithError:error];
  291. }
  292. @end