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.

ZXAztecDetector.m 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  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 "ZXAztecDetector.h"
  17. #import "ZXAztecDetectorResult.h"
  18. #import "ZXErrors.h"
  19. #import "ZXGenericGF.h"
  20. #import "ZXGridSampler.h"
  21. #import "ZXIntArray.h"
  22. #import "ZXMathUtils.h"
  23. #import "ZXReedSolomonDecoder.h"
  24. #import "ZXResultPoint.h"
  25. #import "ZXWhiteRectangleDetector.h"
  26. @implementation ZXAztecPoint
  27. - (id)initWithX:(int)x y:(int)y {
  28. if (self = [super init]) {
  29. _x = x;
  30. _y = y;
  31. }
  32. return self;
  33. }
  34. - (ZXResultPoint *)toResultPoint {
  35. return [[ZXResultPoint alloc] initWithX:self.x y:self.y];
  36. }
  37. - (NSString *)description {
  38. return [NSString stringWithFormat:@"<%d %d>", self.x, self.y];
  39. }
  40. @end
  41. @interface ZXAztecDetector ()
  42. @property (nonatomic, assign, getter = isCompact) BOOL compact;
  43. @property (nonatomic, strong) ZXBitMatrix *image;
  44. @property (nonatomic, assign) int nbCenterLayers;
  45. @property (nonatomic, assign) int nbDataBlocks;
  46. @property (nonatomic, assign) int nbLayers;
  47. @property (nonatomic, assign) int shift;
  48. @end
  49. @implementation ZXAztecDetector
  50. - (id)initWithImage:(ZXBitMatrix *)image {
  51. if (self = [super init]) {
  52. _image = image;
  53. }
  54. return self;
  55. }
  56. - (ZXAztecDetectorResult *)detectWithError:(NSError **)error {
  57. return [self detectWithMirror:NO error:error];
  58. }
  59. - (ZXAztecDetectorResult *)detectWithMirror:(BOOL)isMirror error:(NSError **)error {
  60. // 1. Get the center of the aztec matrix
  61. ZXAztecPoint *pCenter = [self matrixCenter];
  62. if (!pCenter) {
  63. if (error) *error = ZXNotFoundErrorInstance();
  64. return nil;
  65. }
  66. // 2. Get the center points of the four diagonal points just outside the bull's eye
  67. // [topRight, bottomRight, bottomLeft, topLeft]
  68. NSMutableArray *bullsEyeCorners = [self bullsEyeCorners:pCenter];
  69. if (!bullsEyeCorners) {
  70. if (error) *error = ZXNotFoundErrorInstance();
  71. return nil;
  72. }
  73. if (isMirror) {
  74. ZXResultPoint *temp = bullsEyeCorners[0];
  75. bullsEyeCorners[0] = bullsEyeCorners[2];
  76. bullsEyeCorners[2] = temp;
  77. }
  78. // 3. Get the size of the matrix and other parameters from the bull's eye
  79. if (![self extractParameters:bullsEyeCorners]) {
  80. if (error) *error = ZXNotFoundErrorInstance();
  81. return nil;
  82. }
  83. // 4. Sample the grid
  84. ZXBitMatrix *bits = [self sampleGrid:self.image
  85. topLeft:bullsEyeCorners[self.shift % 4]
  86. topRight:bullsEyeCorners[(self.shift + 1) % 4]
  87. bottomRight:bullsEyeCorners[(self.shift + 2) % 4]
  88. bottomLeft:bullsEyeCorners[(self.shift + 3) % 4]];
  89. if (!bits) {
  90. if (error) *error = ZXNotFoundErrorInstance();
  91. return nil;
  92. }
  93. // 5. Get the corners of the matrix.
  94. NSArray *corners = [self matrixCornerPoints:bullsEyeCorners];
  95. if (!corners) {
  96. if (error) *error = ZXNotFoundErrorInstance();
  97. return nil;
  98. }
  99. return [[ZXAztecDetectorResult alloc] initWithBits:bits
  100. points:corners
  101. compact:self.compact
  102. nbDatablocks:self.nbDataBlocks
  103. nbLayers:self.nbLayers];
  104. }
  105. /**
  106. * Extracts the number of data layers and data blocks from the layer around the bull's eye
  107. */
  108. - (BOOL)extractParameters:(NSArray *)bullsEyeCorners {
  109. ZXResultPoint *p0 = bullsEyeCorners[0];
  110. ZXResultPoint *p1 = bullsEyeCorners[1];
  111. ZXResultPoint *p2 = bullsEyeCorners[2];
  112. ZXResultPoint *p3 = bullsEyeCorners[3];
  113. if (![self isValid:p0] || ![self isValid:p1] ||
  114. ![self isValid:p2] || ![self isValid:p3]) {
  115. return NO;
  116. }
  117. int length = 2 * self.nbCenterLayers;
  118. // Get the bits around the bull's eye
  119. int sides[] = {
  120. [self sampleLine:p0 p2:p1 size:length], // Right side
  121. [self sampleLine:p1 p2:p2 size:length], // Bottom
  122. [self sampleLine:p2 p2:p3 size:length], // Left side
  123. [self sampleLine:p3 p2:p0 size:length] // Top
  124. };
  125. // bullsEyeCorners[shift] is the corner of the bulls'eye that has three
  126. // orientation marks.
  127. // sides[shift] is the row/column that goes from the corner with three
  128. // orientation marks to the corner with two.
  129. int shift = [self rotationForSides:sides length:length];
  130. if (shift == -1) {
  131. return NO;
  132. }
  133. self.shift = shift;
  134. // Flatten the parameter bits into a single 28- or 40-bit long
  135. long parameterData = 0;
  136. for (int i = 0; i < 4; i++) {
  137. int side = sides[(self.shift + i) % 4];
  138. if (self.isCompact) {
  139. // Each side of the form ..XXXXXXX. where Xs are parameter data
  140. parameterData <<= 7;
  141. parameterData += (side >> 1) & 0x7F;
  142. } else {
  143. // Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
  144. parameterData <<= 10;
  145. parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
  146. }
  147. }
  148. // Corrects parameter data using RS. Returns just the data portion
  149. // without the error correction.
  150. int correctedData = [self correctedParameterData:parameterData compact:self.isCompact];
  151. if (correctedData == -1) {
  152. return NO;
  153. }
  154. if (self.isCompact) {
  155. // 8 bits: 2 bits layers and 6 bits data blocks
  156. self.nbLayers = (correctedData >> 6) + 1;
  157. self.nbDataBlocks = (correctedData & 0x3F) + 1;
  158. } else {
  159. // 16 bits: 5 bits layers and 11 bits data blocks
  160. self.nbLayers = (correctedData >> 11) + 1;
  161. self.nbDataBlocks = (correctedData & 0x7FF) + 1;
  162. }
  163. return YES;
  164. }
  165. static int expectedCornerBits[] = {
  166. 0xee0, // 07340 XXX .XX X.. ...
  167. 0x1dc, // 00734 ... XXX .XX X..
  168. 0x83b, // 04073 X.. ... XXX .XX
  169. 0x707, // 03407 .XX X.. ... XXX
  170. };
  171. static int bitCount(uint32_t i) {
  172. i = i - ((i >> 1) & 0x55555555);
  173. i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
  174. return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
  175. }
  176. - (int)rotationForSides:(const int[])sides length:(int)length {
  177. // In a normal pattern, we expect to See
  178. // ** .* D A
  179. // * *
  180. //
  181. // . *
  182. // .. .. C B
  183. //
  184. // Grab the 3 bits from each of the sides the form the locator pattern and concatenate
  185. // into a 12-bit integer. Start with the bit at A
  186. int cornerBits = 0;
  187. for (int i = 0; i < 4; i++) {
  188. int side = sides[i];
  189. // XX......X where X's are orientation marks
  190. int t = ((side >> (length - 2)) << 1) + (side & 1);
  191. cornerBits = (cornerBits << 3) + t;
  192. }
  193. // Mov the bottom bit to the top, so that the three bits of the locator pattern at A are
  194. // together. cornerBits is now:
  195. // 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D
  196. cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1);
  197. // The result shift indicates which element of BullsEyeCorners[] goes into the top-left
  198. // corner. Since the four rotation values have a Hamming distance of 8, we
  199. // can easily tolerate two errors.
  200. for (int shift = 0; shift < 4; shift++) {
  201. if (bitCount(cornerBits ^ expectedCornerBits[shift]) <= 2) {
  202. return shift;
  203. }
  204. }
  205. return -1;
  206. }
  207. /**
  208. * Corrects the parameter bits using Reed-Solomon algorithm.
  209. *
  210. * @param parameterData parameter bits
  211. * @param compact true if this is a compact Aztec code
  212. * @return -1 if the array contains too many errors
  213. */
  214. - (int)correctedParameterData:(long)parameterData compact:(BOOL)compact {
  215. int numCodewords;
  216. int numDataCodewords;
  217. if (compact) {
  218. numCodewords = 7;
  219. numDataCodewords = 2;
  220. } else {
  221. numCodewords = 10;
  222. numDataCodewords = 4;
  223. }
  224. int numECCodewords = numCodewords - numDataCodewords;
  225. ZXIntArray *parameterWords = [[ZXIntArray alloc] initWithLength:numCodewords];
  226. for (int i = numCodewords - 1; i >= 0; --i) {
  227. parameterWords.array[i] = (int32_t) parameterData & 0xF;
  228. parameterData >>= 4;
  229. }
  230. ZXReedSolomonDecoder *rsDecoder = [[ZXReedSolomonDecoder alloc] initWithField:[ZXGenericGF AztecParam]];
  231. if (![rsDecoder decode:parameterWords twoS:numECCodewords error:nil]) {
  232. return NO;
  233. }
  234. // Toss the error correction. Just return the data as an integer
  235. int result = 0;
  236. for (int i = 0; i < numDataCodewords; i++) {
  237. result = (result << 4) + parameterWords.array[i];
  238. }
  239. return result;
  240. }
  241. /**
  242. * Finds the corners of a bull-eye centered on the passed point.
  243. * This returns the centers of the diagonal points just outside the bull's eye
  244. * Returns [topRight, bottomRight, bottomLeft, topLeft]
  245. *
  246. * @param pCenter Center point
  247. * @return The corners of the bull-eye, or nil if no valid bull-eye can be found
  248. */
  249. - (NSMutableArray *)bullsEyeCorners:(ZXAztecPoint *)pCenter {
  250. ZXAztecPoint *pina = pCenter;
  251. ZXAztecPoint *pinb = pCenter;
  252. ZXAztecPoint *pinc = pCenter;
  253. ZXAztecPoint *pind = pCenter;
  254. BOOL color = YES;
  255. for (self.nbCenterLayers = 1; self.nbCenterLayers < 9; self.nbCenterLayers++) {
  256. ZXAztecPoint *pouta = [self firstDifferent:pina color:color dx:1 dy:-1];
  257. ZXAztecPoint *poutb = [self firstDifferent:pinb color:color dx:1 dy:1];
  258. ZXAztecPoint *poutc = [self firstDifferent:pinc color:color dx:-1 dy:1];
  259. ZXAztecPoint *poutd = [self firstDifferent:pind color:color dx:-1 dy:-1];
  260. //d a
  261. //
  262. //c b
  263. if (self.nbCenterLayers > 2) {
  264. float q = [self distance:poutd b:pouta] * self.nbCenterLayers / ([self distance:pind b:pina] * (self.nbCenterLayers + 2));
  265. if (q < 0.75 || q > 1.25 || ![self isWhiteOrBlackRectangle:pouta p2:poutb p3:poutc p4:poutd]) {
  266. break;
  267. }
  268. }
  269. pina = pouta;
  270. pinb = poutb;
  271. pinc = poutc;
  272. pind = poutd;
  273. color = !color;
  274. }
  275. if (self.nbCenterLayers != 5 && self.nbCenterLayers != 7) {
  276. return nil;
  277. }
  278. self.compact = self.nbCenterLayers == 5;
  279. // Expand the square by .5 pixel in each direction so that we're on the border
  280. // between the white square and the black square
  281. ZXResultPoint *pinax = [[ZXResultPoint alloc] initWithX:pina.x + 0.5f y:pina.y - 0.5f];
  282. ZXResultPoint *pinbx = [[ZXResultPoint alloc] initWithX:pinb.x + 0.5f y:pinb.y + 0.5f];
  283. ZXResultPoint *pincx = [[ZXResultPoint alloc] initWithX:pinc.x - 0.5f y:pinc.y + 0.5f];
  284. ZXResultPoint *pindx = [[ZXResultPoint alloc] initWithX:pind.x - 0.5f y:pind.y - 0.5f];
  285. // Expand the square so that its corners are the centers of the points
  286. // just outside the bull's eye.
  287. return [[self expandSquare:@[pinax, pinbx, pincx, pindx]
  288. oldSide:2 * self.nbCenterLayers - 3
  289. newSide:2 * self.nbCenterLayers] mutableCopy];
  290. }
  291. /**
  292. * Finds a candidate center point of an Aztec code from an image
  293. */
  294. - (ZXAztecPoint *)matrixCenter {
  295. ZXResultPoint *pointA;
  296. ZXResultPoint *pointB;
  297. ZXResultPoint *pointC;
  298. ZXResultPoint *pointD;
  299. ZXWhiteRectangleDetector *detector = [[ZXWhiteRectangleDetector alloc] initWithImage:self.image error:nil];
  300. NSArray *cornerPoints = [detector detectWithError:nil];
  301. if (cornerPoints) {
  302. pointA = cornerPoints[0];
  303. pointB = cornerPoints[1];
  304. pointC = cornerPoints[2];
  305. pointD = cornerPoints[3];
  306. } else {
  307. // This exception can be in case the initial rectangle is white
  308. // In that case, surely in the bull's eye, we try to expand the rectangle.
  309. int cx = self.image.width / 2;
  310. int cy = self.image.height / 2;
  311. pointA = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy - 7] color:NO dx:1 dy:-1] toResultPoint];
  312. pointB = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy + 7] color:NO dx:1 dy:1] toResultPoint];
  313. pointC = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy + 7] color:NO dx:-1 dy:1] toResultPoint];
  314. pointD = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy - 7] color:NO dx:-1 dy:-1] toResultPoint];
  315. }
  316. //Compute the center of the rectangle
  317. int cx = [ZXMathUtils round:([pointA x] + [pointD x] + [pointB x] + [pointC x]) / 4.0f];
  318. int cy = [ZXMathUtils round:([pointA y] + [pointD y] + [pointB y] + [pointC y]) / 4.0f];
  319. // Redetermine the white rectangle starting from previously computed center.
  320. // This will ensure that we end up with a white rectangle in center bull's eye
  321. // in order to compute a more accurate center.
  322. detector = [[ZXWhiteRectangleDetector alloc] initWithImage:self.image initSize:15 x:cx y:cy error:nil];
  323. cornerPoints = [detector detectWithError:nil];
  324. if (cornerPoints) {
  325. pointA = cornerPoints[0];
  326. pointB = cornerPoints[1];
  327. pointC = cornerPoints[2];
  328. pointD = cornerPoints[3];
  329. } else {
  330. // This exception can be in case the initial rectangle is white
  331. // In that case we try to expand the rectangle.
  332. pointA = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy - 7] color:NO dx:1 dy:-1] toResultPoint];
  333. pointB = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx + 7 y:cy + 7] color:NO dx:1 dy:1] toResultPoint];
  334. pointC = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy + 7] color:NO dx:-1 dy:1] toResultPoint];
  335. pointD = [[self firstDifferent:[[ZXAztecPoint alloc] initWithX:cx - 7 y:cy - 7] color:NO dx:-1 dy:-1] toResultPoint];
  336. }
  337. cx = [ZXMathUtils round:([pointA x] + [pointD x] + [pointB x] + [pointC x]) / 4];
  338. cy = [ZXMathUtils round:([pointA y] + [pointD y] + [pointB y] + [pointC y]) / 4];
  339. // Recompute the center of the rectangle
  340. return [[ZXAztecPoint alloc] initWithX:cx y:cy];
  341. }
  342. /**
  343. * Gets the Aztec code corners from the bull's eye corners and the parameters.
  344. *
  345. * @param bullsEyeCorners the array of bull's eye corners
  346. * @return the array of aztec code corners, or nil if the corner points do not fit in the image
  347. */
  348. - (NSArray *)matrixCornerPoints:(NSArray *)bullsEyeCorners {
  349. return [self expandSquare:bullsEyeCorners oldSide:2 * self.nbCenterLayers newSide:[self dimension]];
  350. }
  351. /**
  352. * Creates a BitMatrix by sampling the provided image.
  353. * topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the
  354. * diagonal just outside the bull's eye.
  355. */
  356. - (ZXBitMatrix *)sampleGrid:(ZXBitMatrix *)anImage
  357. topLeft:(ZXResultPoint *)topLeft
  358. topRight:(ZXResultPoint *)topRight
  359. bottomRight:(ZXResultPoint *)bottomRight
  360. bottomLeft:(ZXResultPoint *)bottomLeft {
  361. ZXGridSampler *sampler = [ZXGridSampler instance];
  362. int dimension = [self dimension];
  363. float low = dimension / 2.0f - self.nbCenterLayers;
  364. float high = dimension / 2.0f + self.nbCenterLayers;
  365. return [sampler sampleGrid:anImage
  366. dimensionX:dimension
  367. dimensionY:dimension
  368. p1ToX:low p1ToY:low // topleft
  369. p2ToX:high p2ToY:low // topright
  370. p3ToX:high p3ToY:high // bottomright
  371. p4ToX:low p4ToY:high // bottomleft
  372. p1FromX:topLeft.x p1FromY:topLeft.y
  373. p2FromX:topRight.x p2FromY:topRight.y
  374. p3FromX:bottomRight.x p3FromY:bottomRight.y
  375. p4FromX:bottomLeft.x p4FromY:bottomLeft.y
  376. error:nil];
  377. }
  378. /**
  379. * Samples a line.
  380. *
  381. * @param p1 start point (inclusive)
  382. * @param p2 end point (exclusive)
  383. * @param size number of bits
  384. * @return the array of bits as an int (first bit is high-order bit of result)
  385. */
  386. - (int)sampleLine:(ZXResultPoint *)p1 p2:(ZXResultPoint *)p2 size:(int)size {
  387. int result = 0;
  388. float d = [self resultDistance:p1 b:p2];
  389. float moduleSize = d / size;
  390. float px = p1.x;
  391. float py = p1.y;
  392. float dx = moduleSize * (p2.x - p1.x) / d;
  393. float dy = moduleSize * (p2.y - p1.y) / d;
  394. for (int i = 0; i < size; i++) {
  395. if ([self.image getX:[ZXMathUtils round:px + i * dx] y:[ZXMathUtils round:py + i * dy]]) {
  396. result |= 1 << (size - i - 1);
  397. }
  398. }
  399. return result;
  400. }
  401. /**
  402. * @return true if the border of the rectangle passed in parameter is compound of white points only
  403. * or black points only
  404. */
  405. - (BOOL)isWhiteOrBlackRectangle:(ZXAztecPoint *)p1 p2:(ZXAztecPoint *)p2 p3:(ZXAztecPoint *)p3 p4:(ZXAztecPoint *)p4 {
  406. int corr = 3;
  407. p1 = [[ZXAztecPoint alloc] initWithX:p1.x - corr y:p1.y + corr];
  408. p2 = [[ZXAztecPoint alloc] initWithX:p2.x - corr y:p2.y - corr];
  409. p3 = [[ZXAztecPoint alloc] initWithX:p3.x + corr y:p3.y - corr];
  410. p4 = [[ZXAztecPoint alloc] initWithX:p4.x + corr y:p4.y + corr];
  411. int cInit = [self color:p4 p2:p1];
  412. if (cInit == 0) {
  413. return NO;
  414. }
  415. int c = [self color:p1 p2:p2];
  416. if (c != cInit) {
  417. return NO;
  418. }
  419. c = [self color:p2 p2:p3];
  420. if (c != cInit) {
  421. return NO;
  422. }
  423. c = [self color:p3 p2:p4];
  424. return c == cInit;
  425. }
  426. /**
  427. * Gets the color of a segment
  428. *
  429. * @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else
  430. */
  431. - (int)color:(ZXAztecPoint *)p1 p2:(ZXAztecPoint *)p2 {
  432. float d = [self distance:p1 b:p2];
  433. float dx = (p2.x - p1.x) / d;
  434. float dy = (p2.y - p1.y) / d;
  435. int error = 0;
  436. float px = p1.x;
  437. float py = p1.y;
  438. BOOL colorModel = [self.image getX:p1.x y:p1.y];
  439. for (int i = 0; i < d; i++) {
  440. px += dx;
  441. py += dy;
  442. if ([self.image getX:[ZXMathUtils round:px] y:[ZXMathUtils round:py]] != colorModel) {
  443. error++;
  444. }
  445. }
  446. float errRatio = (float)error / d;
  447. if (errRatio > 0.1f && errRatio < 0.9f) {
  448. return 0;
  449. }
  450. return (errRatio <= 0.1f) == colorModel ? 1 : -1;
  451. }
  452. /**
  453. * Gets the coordinate of the first point with a different color in the given direction
  454. */
  455. - (ZXAztecPoint *)firstDifferent:(ZXAztecPoint *)init color:(BOOL)color dx:(int)dx dy:(int)dy {
  456. int x = init.x + dx;
  457. int y = init.y + dy;
  458. while ([self isValidX:x y:y] && [self.image getX:x y:y] == color) {
  459. x += dx;
  460. y += dy;
  461. }
  462. x -= dx;
  463. y -= dy;
  464. while ([self isValidX:x y:y] && [self.image getX:x y:y] == color) {
  465. x += dx;
  466. }
  467. x -= dx;
  468. while ([self isValidX:x y:y] && [self.image getX:x y:y] == color) {
  469. y += dy;
  470. }
  471. y -= dy;
  472. return [[ZXAztecPoint alloc] initWithX:x y:y];
  473. }
  474. /**
  475. * Expand the square represented by the corner points by pushing out equally in all directions
  476. *
  477. * @param cornerPoints the corners of the square, which has the bull's eye at its center
  478. * @param oldSide the original length of the side of the square in the target bit matrix
  479. * @param newSide the new length of the size of the square in the target bit matrix
  480. * @return the corners of the expanded square
  481. */
  482. - (NSArray *)expandSquare:(NSArray *)cornerPoints oldSide:(float)oldSide newSide:(float)newSide {
  483. ZXResultPoint *cornerPoints0 = (ZXResultPoint *)cornerPoints[0];
  484. ZXResultPoint *cornerPoints1 = (ZXResultPoint *)cornerPoints[1];
  485. ZXResultPoint *cornerPoints2 = (ZXResultPoint *)cornerPoints[2];
  486. ZXResultPoint *cornerPoints3 = (ZXResultPoint *)cornerPoints[3];
  487. float ratio = newSide / (2 * oldSide);
  488. float dx = cornerPoints0.x - cornerPoints2.x;
  489. float dy = cornerPoints0.y - cornerPoints2.y;
  490. float centerx = (cornerPoints0.x + cornerPoints2.x) / 2.0f;
  491. float centery = (cornerPoints0.y + cornerPoints2.y) / 2.0f;
  492. ZXResultPoint *result0 = [[ZXResultPoint alloc] initWithX:centerx + ratio * dx y:centery + ratio * dy];
  493. ZXResultPoint *result2 = [[ZXResultPoint alloc] initWithX:centerx - ratio * dx y:centery - ratio * dy];
  494. dx = cornerPoints1.x - cornerPoints3.x;
  495. dy = cornerPoints1.y - cornerPoints3.y;
  496. centerx = (cornerPoints1.x + cornerPoints3.x) / 2.0f;
  497. centery = (cornerPoints1.y + cornerPoints3.y) / 2.0f;
  498. ZXResultPoint *result1 = [[ZXResultPoint alloc] initWithX:centerx + ratio * dx y:centery + ratio * dy];
  499. ZXResultPoint *result3 = [[ZXResultPoint alloc] initWithX:centerx - ratio * dx y:centery - ratio * dy];
  500. return @[result0, result1, result2, result3];
  501. }
  502. - (BOOL)isValidX:(int)x y:(int)y {
  503. return x >= 0 && x < self.image.width && y > 0 && y < self.image.height;
  504. }
  505. - (BOOL)isValid:(ZXResultPoint *)point {
  506. int x = [ZXMathUtils round:point.x];
  507. int y = [ZXMathUtils round:point.y];
  508. return [self isValidX:x y:y];
  509. }
  510. - (float)distance:(ZXAztecPoint *)a b:(ZXAztecPoint *)b {
  511. return [ZXMathUtils distance:a.x aY:a.y bX:b.x bY:b.y];
  512. }
  513. - (float)resultDistance:(ZXResultPoint *)a b:(ZXResultPoint *)b {
  514. return [ZXMathUtils distance:a.x aY:a.y bX:b.x bY:b.y];
  515. }
  516. - (int)dimension {
  517. if (self.compact) {
  518. return 4 * self.nbLayers + 11;
  519. }
  520. if (self.nbLayers <= 4) {
  521. return 4 * self.nbLayers + 15;
  522. }
  523. return 4 * self.nbLayers + 2 * ((self.nbLayers-4)/8 + 1) + 15;
  524. }
  525. @end