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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 "ZXPDF417BarcodeMetadata.h"
  17. #import "ZXPDF417BoundingBox.h"
  18. #import "ZXPDF417Codeword.h"
  19. #import "ZXPDF417Common.h"
  20. #import "ZXPDF417DetectionResult.h"
  21. #import "ZXPDF417DetectionResultColumn.h"
  22. #import "ZXPDF417DetectionResultRowIndicatorColumn.h"
  23. const int ZX_PDF417_ADJUST_ROW_NUMBER_SKIP = 2;
  24. @interface ZXPDF417DetectionResult ()
  25. @property (nonatomic, strong, readonly) ZXPDF417BarcodeMetadata *barcodeMetadata;
  26. @property (nonatomic, strong, readonly) NSMutableArray *detectionResultColumnsInternal;
  27. @property (nonatomic, assign, readonly) int barcodeColumnCount;
  28. @end
  29. @implementation ZXPDF417DetectionResult
  30. - (id)initWithBarcodeMetadata:(ZXPDF417BarcodeMetadata *)barcodeMetadata boundingBox:(ZXPDF417BoundingBox *)boundingBox {
  31. self = [super init];
  32. if (self) {
  33. _barcodeMetadata = barcodeMetadata;
  34. _barcodeColumnCount = barcodeMetadata.columnCount;
  35. _boundingBox = boundingBox;
  36. _detectionResultColumnsInternal = [NSMutableArray arrayWithCapacity:_barcodeColumnCount + 2];
  37. for (int i = 0; i < _barcodeColumnCount + 2; i++) {
  38. [_detectionResultColumnsInternal addObject:[NSNull null]];
  39. }
  40. }
  41. return self;
  42. }
  43. - (NSArray *)detectionResultColumns {
  44. [self adjustIndicatorColumnRowNumbers:self.detectionResultColumnsInternal[0]];
  45. [self adjustIndicatorColumnRowNumbers:self.detectionResultColumnsInternal[self.barcodeColumnCount + 1]];
  46. int unadjustedCodewordCount = ZX_PDF417_MAX_CODEWORDS_IN_BARCODE;
  47. int previousUnadjustedCount;
  48. do {
  49. previousUnadjustedCount = unadjustedCodewordCount;
  50. unadjustedCodewordCount = [self adjustRowNumbers];
  51. } while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount);
  52. return self.detectionResultColumnsInternal;
  53. }
  54. - (void)adjustIndicatorColumnRowNumbers:(ZXPDF417DetectionResultColumn *)detectionResultColumn {
  55. if (detectionResultColumn && (id)detectionResultColumn != [NSNull null]) {
  56. [(ZXPDF417DetectionResultRowIndicatorColumn *)detectionResultColumn adjustCompleteIndicatorColumnRowNumbers:self.barcodeMetadata];
  57. }
  58. }
  59. // TODO ensure that no detected codewords with unknown row number are left
  60. // we should be able to estimate the row height and use it as a hint for the row number
  61. // we should also fill the rows top to bottom and bottom to top
  62. /**
  63. * @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords
  64. * will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers
  65. */
  66. - (int)adjustRowNumbers {
  67. int unadjustedCount = [self adjustRowNumbersByRow];
  68. if (unadjustedCount == 0) {
  69. return 0;
  70. }
  71. for (int barcodeColumn = 1; barcodeColumn < self.barcodeColumnCount + 1; barcodeColumn++) {
  72. NSArray *codewords = [self.detectionResultColumnsInternal[barcodeColumn] codewords];
  73. for (int codewordsRow = 0; codewordsRow < [codewords count]; codewordsRow++) {
  74. if ((id)codewords[codewordsRow] == [NSNull null]) {
  75. continue;
  76. }
  77. if (![codewords[codewordsRow] hasValidRowNumber]) {
  78. [self adjustRowNumbers:barcodeColumn codewordsRow:codewordsRow codewords:codewords];
  79. }
  80. }
  81. }
  82. return unadjustedCount;
  83. }
  84. - (int)adjustRowNumbersByRow {
  85. [self adjustRowNumbersFromBothRI];
  86. // TODO we should only do full row adjustments if row numbers of left and right row indicator column match.
  87. // Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode
  88. // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row
  89. // number starts and ends.
  90. int unadjustedCount = [self adjustRowNumbersFromLRI];
  91. return unadjustedCount + [self adjustRowNumbersFromRRI];
  92. }
  93. - (void)adjustRowNumbersFromBothRI {
  94. if (self.detectionResultColumnsInternal[0] == [NSNull null] || self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] == [NSNull null]) {
  95. return;
  96. }
  97. NSArray *LRIcodewords = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[0] codewords];
  98. NSArray *RRIcodewords = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] codewords];
  99. for (int codewordsRow = 0; codewordsRow < [LRIcodewords count]; codewordsRow++) {
  100. if (LRIcodewords[codewordsRow] != [NSNull null] &&
  101. RRIcodewords[codewordsRow] != [NSNull null] &&
  102. [(ZXPDF417Codeword *)LRIcodewords[codewordsRow] rowNumber] == [(ZXPDF417Codeword *)RRIcodewords[codewordsRow] rowNumber]) {
  103. for (int barcodeColumn = 1; barcodeColumn <= self.barcodeColumnCount; barcodeColumn++) {
  104. ZXPDF417Codeword *codeword = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
  105. if ((id)codeword == [NSNull null]) {
  106. continue;
  107. }
  108. codeword.rowNumber = [(ZXPDF417Codeword *)LRIcodewords[codewordsRow] rowNumber];
  109. if (![codeword hasValidRowNumber]) {
  110. [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow] = [NSNull null];
  111. }
  112. }
  113. }
  114. }
  115. }
  116. - (int)adjustRowNumbersFromRRI {
  117. if (self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] == [NSNull null]) {
  118. return 0;
  119. }
  120. int unadjustedCount = 0;
  121. NSArray *codewords = [self.detectionResultColumnsInternal[self.barcodeColumnCount + 1] codewords];
  122. for (int codewordsRow = 0; codewordsRow < [codewords count]; codewordsRow++) {
  123. if ((id)codewords[codewordsRow] == [NSNull null]) {
  124. continue;
  125. }
  126. int rowIndicatorRowNumber = [codewords[codewordsRow] rowNumber];
  127. int invalidRowCounts = 0;
  128. for (int barcodeColumn = self.barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ZX_PDF417_ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) {
  129. if (self.detectionResultColumnsInternal[barcodeColumn] != [NSNull null]) {
  130. ZXPDF417Codeword *codeword = [self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
  131. if ((id)codeword != [NSNull null]) {
  132. invalidRowCounts = [self adjustRowNumberIfValid:rowIndicatorRowNumber invalidRowCounts:invalidRowCounts codeword:codeword];
  133. if (![codeword hasValidRowNumber]) {
  134. unadjustedCount++;
  135. }
  136. }
  137. }
  138. }
  139. }
  140. return unadjustedCount;
  141. }
  142. - (int)adjustRowNumbersFromLRI {
  143. if (self.detectionResultColumnsInternal[0] == [NSNull null]) {
  144. return 0;
  145. }
  146. int unadjustedCount = 0;
  147. NSArray *codewords = [self.detectionResultColumnsInternal[0] codewords];
  148. for (int codewordsRow = 0; codewordsRow < [codewords count]; codewordsRow++) {
  149. if ((id)codewords[codewordsRow] == [NSNull null]) {
  150. continue;
  151. }
  152. int rowIndicatorRowNumber = [codewords[codewordsRow] rowNumber];
  153. int invalidRowCounts = 0;
  154. for (int barcodeColumn = 1; barcodeColumn < self.barcodeColumnCount + 1 && invalidRowCounts < ZX_PDF417_ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) {
  155. if (self.detectionResultColumnsInternal[barcodeColumn] != [NSNull null]) {
  156. ZXPDF417Codeword *codeword = [self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
  157. if ((id)codeword != [NSNull null]) {
  158. invalidRowCounts = [self adjustRowNumberIfValid:rowIndicatorRowNumber invalidRowCounts:invalidRowCounts codeword:codeword];
  159. if (![codeword hasValidRowNumber]) {
  160. unadjustedCount++;
  161. }
  162. }
  163. }
  164. }
  165. }
  166. return unadjustedCount;
  167. }
  168. - (int)adjustRowNumberIfValid:(int)rowIndicatorRowNumber invalidRowCounts:(int)invalidRowCounts codeword:(ZXPDF417Codeword *)codeword {
  169. if (!codeword) {
  170. return invalidRowCounts;
  171. }
  172. if (![codeword hasValidRowNumber]) {
  173. if ([codeword isValidRowNumber:rowIndicatorRowNumber]) {
  174. [codeword setRowNumber:rowIndicatorRowNumber];
  175. invalidRowCounts = 0;
  176. } else {
  177. ++invalidRowCounts;
  178. }
  179. }
  180. return invalidRowCounts;
  181. }
  182. - (void)adjustRowNumbers:(int)barcodeColumn codewordsRow:(int)codewordsRow codewords:(NSArray *)codewords {
  183. ZXPDF417Codeword *codeword = codewords[codewordsRow];
  184. NSArray *previousColumnCodewords = [self.detectionResultColumnsInternal[barcodeColumn - 1] codewords];
  185. NSArray *nextColumnCodewords = previousColumnCodewords;
  186. if (self.detectionResultColumnsInternal[barcodeColumn + 1] != [NSNull null]) {
  187. nextColumnCodewords = [self.detectionResultColumnsInternal[barcodeColumn + 1] codewords];
  188. }
  189. NSMutableArray *otherCodewords = [NSMutableArray arrayWithCapacity:14];
  190. for (int i = 0; i < 14; i++) {
  191. [otherCodewords addObject:[NSNull null]];
  192. }
  193. otherCodewords[2] = previousColumnCodewords[codewordsRow];
  194. otherCodewords[3] = nextColumnCodewords[codewordsRow];
  195. if (codewordsRow > 0) {
  196. otherCodewords[0] = codewords[codewordsRow - 1];
  197. otherCodewords[4] = previousColumnCodewords[codewordsRow - 1];
  198. otherCodewords[5] = nextColumnCodewords[codewordsRow - 1];
  199. }
  200. if (codewordsRow > 1) {
  201. otherCodewords[8] = codewords[codewordsRow - 2];
  202. otherCodewords[10] = previousColumnCodewords[codewordsRow - 2];
  203. otherCodewords[11] = nextColumnCodewords[codewordsRow - 2];
  204. }
  205. if (codewordsRow < [codewords count] - 1) {
  206. otherCodewords[1] = codewords[codewordsRow + 1];
  207. otherCodewords[6] = previousColumnCodewords[codewordsRow + 1];
  208. otherCodewords[7] = nextColumnCodewords[codewordsRow + 1];
  209. }
  210. if (codewordsRow < [codewords count] - 2) {
  211. otherCodewords[9] = codewords[codewordsRow + 2];
  212. otherCodewords[12] = previousColumnCodewords[codewordsRow + 2];
  213. otherCodewords[13] = nextColumnCodewords[codewordsRow + 2];
  214. }
  215. for (ZXPDF417Codeword *otherCodeword in otherCodewords) {
  216. if ([self adjustRowNumber:codeword otherCodeword:otherCodeword]) {
  217. return;
  218. }
  219. }
  220. }
  221. /**
  222. * @return true, if row number was adjusted, false otherwise
  223. */
  224. - (BOOL)adjustRowNumber:(ZXPDF417Codeword *)codeword otherCodeword:(ZXPDF417Codeword *)otherCodeword {
  225. if ((id)otherCodeword == [NSNull null]) {
  226. return NO;
  227. }
  228. if ([otherCodeword hasValidRowNumber] && otherCodeword.bucket == codeword.bucket) {
  229. [codeword setRowNumber:otherCodeword.rowNumber];
  230. return YES;
  231. }
  232. return NO;
  233. }
  234. - (int)barcodeRowCount {
  235. return self.barcodeMetadata.rowCount;
  236. }
  237. - (int)barcodeECLevel {
  238. return self.barcodeMetadata.errorCorrectionLevel;
  239. }
  240. - (void)setDetectionResultColumn:(int)barcodeColumn detectionResultColumn:(ZXPDF417DetectionResultColumn *)detectionResultColumn {
  241. if (!detectionResultColumn) {
  242. self.detectionResultColumnsInternal[barcodeColumn] = [NSNull null];
  243. } else {
  244. self.detectionResultColumnsInternal[barcodeColumn] = detectionResultColumn;
  245. }
  246. }
  247. - (ZXPDF417DetectionResultColumn *)detectionResultColumn:(int)barcodeColumn {
  248. ZXPDF417DetectionResultColumn *result = self.detectionResultColumnsInternal[barcodeColumn];
  249. return (id)result == [NSNull null] ? nil : result;
  250. }
  251. - (NSString *)description {
  252. ZXPDF417DetectionResultColumn *rowIndicatorColumn = self.detectionResultColumnsInternal[0];
  253. if ((id)rowIndicatorColumn == [NSNull null]) {
  254. rowIndicatorColumn = self.detectionResultColumnsInternal[self.barcodeColumnCount + 1];
  255. }
  256. NSMutableString *result = [NSMutableString string];
  257. for (int codewordsRow = 0; codewordsRow < [rowIndicatorColumn.codewords count]; codewordsRow++) {
  258. [result appendFormat:@"CW %3d:", codewordsRow];
  259. for (int barcodeColumn = 0; barcodeColumn < self.barcodeColumnCount + 2; barcodeColumn++) {
  260. if (self.detectionResultColumnsInternal[barcodeColumn] == [NSNull null]) {
  261. [result appendString:@" | "];
  262. continue;
  263. }
  264. ZXPDF417Codeword *codeword = [(ZXPDF417DetectionResultColumn *)self.detectionResultColumnsInternal[barcodeColumn] codewords][codewordsRow];
  265. if ((id)codeword == [NSNull null]) {
  266. [result appendString:@" | "];
  267. continue;
  268. }
  269. [result appendFormat:@" %3d|%3d", codeword.rowNumber, codeword.value];
  270. }
  271. [result appendString:@"\n"];
  272. }
  273. return [NSString stringWithString:result];
  274. }
  275. @end