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.

ZXPDF417DetectionResultRowIndicatorColumn.m 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. /*
  2. * Copyright 2013 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 "ZXIntArray.h"
  17. #import "ZXPDF417BarcodeMetadata.h"
  18. #import "ZXPDF417BarcodeValue.h"
  19. #import "ZXPDF417BoundingBox.h"
  20. #import "ZXPDF417Codeword.h"
  21. #import "ZXPDF417Common.h"
  22. #import "ZXPDF417DetectionResult.h"
  23. #import "ZXPDF417DetectionResultRowIndicatorColumn.h"
  24. #import "ZXResultPoint.h"
  25. @implementation ZXPDF417DetectionResultRowIndicatorColumn
  26. - (id)initWithBoundingBox:(ZXPDF417BoundingBox *)boundingBox isLeft:(BOOL)isLeft {
  27. self = [super initWithBoundingBox:boundingBox];
  28. if (self) {
  29. _isLeft = isLeft;
  30. }
  31. return self;
  32. }
  33. - (void)setRowNumbers {
  34. for (ZXPDF417Codeword *codeword in [self codewords]) {
  35. if ((id)codeword != [NSNull null]) {
  36. [codeword setRowNumberAsRowIndicatorColumn];
  37. }
  38. }
  39. }
  40. // TODO implement properly
  41. // TODO maybe we should add missing codewords to store the correct row number to make
  42. // finding row numbers for other columns easier
  43. // use row height count to make detection of invalid row numbers more reliable
  44. - (int)adjustCompleteIndicatorColumnRowNumbers:(ZXPDF417BarcodeMetadata *)barcodeMetadata {
  45. [self setRowNumbers];
  46. [self removeIncorrectCodewords:barcodeMetadata];
  47. ZXResultPoint *top = self.isLeft ? self.boundingBox.topLeft : self.boundingBox.topRight;
  48. ZXResultPoint *bottom = self.isLeft ? self.boundingBox.bottomLeft : self.boundingBox.bottomRight;
  49. int firstRow = [self imageRowToCodewordIndex:(int) top.y];
  50. int lastRow = [self imageRowToCodewordIndex:(int) bottom.y];
  51. // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and
  52. // taller rows
  53. float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.rowCount;
  54. int barcodeRow = -1;
  55. int maxRowHeight = 1;
  56. int currentRowHeight = 0;
  57. for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
  58. if (self.codewords[codewordsRow] == [NSNull null]) {
  59. continue;
  60. }
  61. ZXPDF417Codeword *codeword = self.codewords[codewordsRow];
  62. // float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight;
  63. // if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) {
  64. // SimpleLog.log(LEVEL.WARNING,
  65. // "Removing codeword, rowNumberSkew too high, codeword[" + codewordsRow + "]: Expected Row: " +
  66. // expectedRowNumber + ", RealRow: " + codeword.getRowNumber() + ", value: " + codeword.getValue());
  67. // codewords[codewordsRow] = null;
  68. // }
  69. int rowDifference = codeword.rowNumber - barcodeRow;
  70. // TODO improve handling with case where first row indicator doesn't start with 0
  71. if (rowDifference == 0) {
  72. currentRowHeight++;
  73. } else if (rowDifference == 1) {
  74. maxRowHeight = MAX(maxRowHeight, currentRowHeight);
  75. currentRowHeight = 1;
  76. barcodeRow = codeword.rowNumber;
  77. } else if (rowDifference < 0 ||
  78. codeword.rowNumber >= barcodeMetadata.rowCount ||
  79. rowDifference > codewordsRow) {
  80. self.codewords[codewordsRow] = [NSNull null];
  81. } else {
  82. int checkedRows;
  83. if (maxRowHeight > 2) {
  84. checkedRows = (maxRowHeight - 2) * rowDifference;
  85. } else {
  86. checkedRows = rowDifference;
  87. }
  88. BOOL closePreviousCodewordFound = checkedRows >= codewordsRow;
  89. for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) {
  90. // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1.
  91. // This should hopefully get rid of most problems already.
  92. closePreviousCodewordFound = self.codewords[codewordsRow - i] != [NSNull null];
  93. }
  94. if (closePreviousCodewordFound) {
  95. self.codewords[codewordsRow] = [NSNull null];
  96. } else {
  97. barcodeRow = codeword.rowNumber;
  98. currentRowHeight = 1;
  99. }
  100. }
  101. }
  102. return (int) (averageRowHeight + 0.5);
  103. }
  104. - (BOOL)getRowHeights:(ZXIntArray **)rowHeights {
  105. ZXPDF417BarcodeMetadata *barcodeMetadata = [self barcodeMetadata];
  106. if (!barcodeMetadata) {
  107. *rowHeights = nil;
  108. return YES;
  109. }
  110. [self adjustIncompleteIndicatorColumnRowNumbers:barcodeMetadata];
  111. ZXIntArray *result = [[ZXIntArray alloc] initWithLength:barcodeMetadata.rowCount];
  112. for (ZXPDF417Codeword *codeword in [self codewords]) {
  113. if ((id)codeword != [NSNull null]) {
  114. int rowNumber = codeword.rowNumber;
  115. if (rowNumber >= result.length) {
  116. *rowHeights = nil;
  117. return NO;
  118. }
  119. result.array[rowNumber]++;
  120. } // else throw exception?
  121. }
  122. *rowHeights = result;
  123. return YES;
  124. }
  125. // TODO maybe we should add missing codewords to store the correct row number to make
  126. // finding row numbers for other columns easier
  127. // use row height count to make detection of invalid row numbers more reliable
  128. - (int)adjustIncompleteIndicatorColumnRowNumbers:(ZXPDF417BarcodeMetadata *)barcodeMetadata {
  129. ZXResultPoint *top = self.isLeft ? self.boundingBox.topLeft : self.boundingBox.topRight;
  130. ZXResultPoint *bottom = self.isLeft ? self.boundingBox.bottomLeft : self.boundingBox.bottomRight;
  131. int firstRow = [self imageRowToCodewordIndex:(int) top.y];
  132. int lastRow = [self imageRowToCodewordIndex:(int) bottom.y];
  133. float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.rowCount;
  134. int barcodeRow = -1;
  135. int maxRowHeight = 1;
  136. int currentRowHeight = 0;
  137. for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
  138. if (self.codewords[codewordsRow] == [NSNull null]) {
  139. continue;
  140. }
  141. ZXPDF417Codeword *codeword = self.codewords[codewordsRow];
  142. [codeword setRowNumberAsRowIndicatorColumn];
  143. int rowDifference = codeword.rowNumber - barcodeRow;
  144. // TODO improve handling with case where first row indicator doesn't start with 0
  145. if (rowDifference == 0) {
  146. currentRowHeight++;
  147. } else if (rowDifference == 1) {
  148. maxRowHeight = MAX(maxRowHeight, currentRowHeight);
  149. currentRowHeight = 1;
  150. barcodeRow = codeword.rowNumber;
  151. } else if (codeword.rowNumber >= barcodeMetadata.rowCount) {
  152. self.codewords[codewordsRow] = [NSNull null];
  153. } else {
  154. barcodeRow = codeword.rowNumber;
  155. currentRowHeight = 1;
  156. }
  157. }
  158. return (int) (averageRowHeight + 0.5);
  159. }
  160. - (ZXPDF417BarcodeMetadata *)barcodeMetadata {
  161. ZXPDF417BarcodeValue *barcodeColumnCount = [[ZXPDF417BarcodeValue alloc] init];
  162. ZXPDF417BarcodeValue *barcodeRowCountUpperPart = [[ZXPDF417BarcodeValue alloc] init];
  163. ZXPDF417BarcodeValue *barcodeRowCountLowerPart = [[ZXPDF417BarcodeValue alloc] init];
  164. ZXPDF417BarcodeValue *barcodeECLevel = [[ZXPDF417BarcodeValue alloc] init];
  165. for (ZXPDF417Codeword *codeword in self.codewords) {
  166. if ((id)codeword == [NSNull null]) {
  167. continue;
  168. }
  169. [codeword setRowNumberAsRowIndicatorColumn];
  170. int rowIndicatorValue = codeword.value % 30;
  171. int codewordRowNumber = codeword.rowNumber;
  172. if (!self.isLeft) {
  173. codewordRowNumber += 2;
  174. }
  175. switch (codewordRowNumber % 3) {
  176. case 0:
  177. [barcodeRowCountUpperPart setValue:rowIndicatorValue * 3 + 1];
  178. break;
  179. case 1:
  180. [barcodeECLevel setValue:rowIndicatorValue / 3];
  181. [barcodeRowCountLowerPart setValue:rowIndicatorValue % 3];
  182. break;
  183. case 2:
  184. [barcodeColumnCount setValue:rowIndicatorValue + 1];
  185. break;
  186. }
  187. }
  188. // Maybe we should check if we have ambiguous values?
  189. if (([barcodeColumnCount value].length == 0) ||
  190. ([barcodeRowCountUpperPart value].length == 0) ||
  191. ([barcodeRowCountLowerPart value].length == 0) ||
  192. ([barcodeECLevel value].length == 0) ||
  193. [barcodeColumnCount value].array[0] < 1 ||
  194. [barcodeRowCountUpperPart value].array[0] + [barcodeRowCountLowerPart value].array[0] < ZX_PDF417_MIN_ROWS_IN_BARCODE ||
  195. [barcodeRowCountUpperPart value].array[0] + [barcodeRowCountLowerPart value].array[0] > ZX_PDF417_MAX_ROWS_IN_BARCODE) {
  196. return nil;
  197. }
  198. ZXPDF417BarcodeMetadata *barcodeMetadata = [[ZXPDF417BarcodeMetadata alloc] initWithColumnCount:[barcodeColumnCount value].array[0]
  199. rowCountUpperPart:[barcodeRowCountUpperPart value].array[0]
  200. rowCountLowerPart:[barcodeRowCountLowerPart value].array[0]
  201. errorCorrectionLevel:[barcodeECLevel value].array[0]];
  202. [self removeIncorrectCodewords:barcodeMetadata];
  203. return barcodeMetadata;
  204. }
  205. - (void)removeIncorrectCodewords:(ZXPDF417BarcodeMetadata *)barcodeMetadata {
  206. // Remove codewords which do not match the metadata
  207. // TODO Maybe we should keep the incorrect codewords for the start and end positions?
  208. for (int codewordRow = 0; codewordRow < [self.codewords count]; codewordRow++) {
  209. ZXPDF417Codeword *codeword = self.codewords[codewordRow];
  210. if (self.codewords[codewordRow] == [NSNull null]) {
  211. continue;
  212. }
  213. int rowIndicatorValue = codeword.value % 30;
  214. int codewordRowNumber = codeword.rowNumber;
  215. if (codewordRowNumber > barcodeMetadata.rowCount) {
  216. self.codewords[codewordRow] = [NSNull null];
  217. continue;
  218. }
  219. if (!self.isLeft) {
  220. codewordRowNumber += 2;
  221. }
  222. switch (codewordRowNumber % 3) {
  223. case 0:
  224. if (rowIndicatorValue * 3 + 1 != barcodeMetadata.rowCountUpperPart) {
  225. self.codewords[codewordRow] = [NSNull null];
  226. }
  227. break;
  228. case 1:
  229. if (rowIndicatorValue / 3 != barcodeMetadata.errorCorrectionLevel ||
  230. rowIndicatorValue % 3 != barcodeMetadata.rowCountLowerPart) {
  231. self.codewords[codewordRow] = [NSNull null];
  232. }
  233. break;
  234. case 2:
  235. if (rowIndicatorValue + 1 != barcodeMetadata.columnCount) {
  236. self.codewords[codewordRow] = [NSNull null];
  237. }
  238. break;
  239. }
  240. }
  241. }
  242. - (NSString *)description {
  243. return [NSString stringWithFormat:@"IsLeft: %@\n%@", @(self.isLeft), [super description]];
  244. }
  245. @end