ea66cad09ecb29965045378ce626ce0eb7bb0fed
[zxing.git] / core / src / com / google / zxing / qrcode / detector / FinderPatternFinder.java
1 /*\r
2  * Copyright 2007 ZXing authors\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *      http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 package com.google.zxing.qrcode.detector;\r
18 \r
19 import com.google.zxing.DecodeHintType;\r
20 import com.google.zxing.MonochromeBitmapSource;\r
21 import com.google.zxing.ReaderException;\r
22 import com.google.zxing.common.BitArray;\r
23 import com.google.zxing.common.Collections;\r
24 import com.google.zxing.common.Comparator;\r
25 import com.google.zxing.common.GenericResultPoint;\r
26 \r
27 import java.util.Hashtable;\r
28 import java.util.Vector;\r
29 \r
30 /**\r
31  * <p>This class attempts to find finder patterns in a QR Code. Finder patterns are the square\r
32  * markers at three corners of a QR Code.</p>\r
33  *\r
34  * <p>This class is not thread-safe and should not be reused.</p>\r
35  *\r
36  * @author srowen@google.com (Sean Owen)\r
37  */\r
38 final class FinderPatternFinder {\r
39 \r
40   private static final int CENTER_QUORUM = 2;\r
41   private static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center\r
42   private static final int MAX_MODULES = 57; // support up to version 10 for mobile clients\r
43   private static final int INTEGER_MATH_SHIFT = 8;\r
44 \r
45   private final MonochromeBitmapSource image;\r
46   private final Vector possibleCenters;\r
47   private boolean hasSkipped;\r
48 \r
49   /**\r
50    * <p>Creates a finder that will search the image for three finder patterns.</p>\r
51    *\r
52    * @param image image to search\r
53    */\r
54   FinderPatternFinder(MonochromeBitmapSource image) {\r
55     this.image = image;\r
56     this.possibleCenters = new Vector();\r
57   }\r
58 \r
59   FinderPatternInfo find(Hashtable hints) throws ReaderException {\r
60     boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);\r
61     int maxI = image.getHeight();\r
62     int maxJ = image.getWidth();\r
63     // We are looking for black/white/black/white/black modules in\r
64     // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far\r
65 \r
66     // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the\r
67     // image, and then account for the center being 3 modules in size. This gives the smallest\r
68     // number of pixels the center could be, so skip this often. When trying harder, look for all\r
69     // QR versions regardless of how dense they are.\r
70     int iSkip = (int) (maxI / (MAX_MODULES * 4.0f) * 3);\r
71     if (iSkip < MIN_SKIP || tryHarder) {\r
72       iSkip = MIN_SKIP;\r
73     }\r
74 \r
75     boolean done = false;\r
76     int[] stateCount = new int[5];\r
77     for (int i = iSkip - 1; i < maxI && !done; i += iSkip) {\r
78       // Get a row of black/white values\r
79       BitArray blackRow = image.getBlackRow(i, null, 0, maxJ);\r
80       stateCount[0] = 0;\r
81       stateCount[1] = 0;\r
82       stateCount[2] = 0;\r
83       stateCount[3] = 0;\r
84       stateCount[4] = 0;\r
85       int currentState = 0;\r
86       for (int j = 0; j < maxJ; j++) {\r
87         if (blackRow.get(j)) {\r
88           // Black pixel\r
89           if ((currentState & 1) == 1) { // Counting white pixels\r
90             currentState++;\r
91           }\r
92           stateCount[currentState]++;\r
93         } else { // White pixel\r
94           if ((currentState & 1) == 0) { // Counting black pixels\r
95             if (currentState == 4) { // A winner?\r
96               if (foundPatternCross(stateCount)) { // Yes\r
97                 boolean confirmed = handlePossibleCenter(stateCount, i, j);\r
98                 if (confirmed) {\r
99                   iSkip = 1; // Go back to examining each line\r
100                   if (hasSkipped) {\r
101                     done = haveMulitplyConfirmedCenters();\r
102                   } else {\r
103                     int rowSkip = findRowSkip();\r
104                     if (rowSkip > stateCount[2]) {\r
105                       // Skip rows between row of lower confirmed center\r
106                       // and top of presumed third confirmed center\r
107                       // but back up a bit to get a full chance of detecting\r
108                       // it, entire width of center of finder pattern\r
109 \r
110                       // Skip by rowSkip, but back off by stateCount[2] (size of last center\r
111                       // of pattern we saw) to be conservative, and also back off by iSkip which\r
112                       // is about to be re-added\r
113                       i += rowSkip - stateCount[2] - iSkip;\r
114                       j = maxJ - 1;\r
115                     }\r
116                   }\r
117                 } else {\r
118                   // Advance to next black pixel\r
119                   do {\r
120                     j++;\r
121                   } while (j < maxJ && !blackRow.get(j));\r
122                   j--; // back up to that last white pixel\r
123                 }\r
124                 // Clear state to start looking again\r
125                 currentState = 0;\r
126                 stateCount[0] = 0;\r
127                 stateCount[1] = 0;\r
128                 stateCount[2] = 0;\r
129                 stateCount[3] = 0;\r
130                 stateCount[4] = 0;\r
131               } else { // No, shift counts back by two\r
132                 stateCount[0] = stateCount[2];\r
133                 stateCount[1] = stateCount[3];\r
134                 stateCount[2] = stateCount[4];\r
135                 stateCount[3] = 1;\r
136                 stateCount[4] = 0;\r
137                 currentState = 3;\r
138               }\r
139             } else {\r
140               stateCount[++currentState]++;\r
141             }\r
142           } else { // Counting white pixels\r
143             stateCount[currentState]++;\r
144           }\r
145         }\r
146       }\r
147       if (foundPatternCross(stateCount)) {\r
148         boolean confirmed = handlePossibleCenter(stateCount, i, maxJ);\r
149         if (confirmed) {\r
150           iSkip = stateCount[0];\r
151           if (hasSkipped) {\r
152             // Found a third one\r
153             done = haveMulitplyConfirmedCenters();\r
154           }\r
155         }\r
156       }\r
157     }\r
158 \r
159     FinderPattern[] patternInfo = selectBestPatterns();\r
160     GenericResultPoint.orderBestPatterns(patternInfo);\r
161 \r
162     return new FinderPatternInfo(patternInfo);\r
163   }\r
164 \r
165   /**\r
166    * Given a count of black/white/black/white/black pixels just seen and an end position,\r
167    * figures the location of the center of this run.\r
168    */\r
169   private static float centerFromEnd(int[] stateCount, int end) {\r
170     return (float) (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;\r
171   }\r
172 \r
173   /**\r
174    * @param stateCount count of black/white/black/white/black pixels just read\r
175    * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios\r
176    *         used by finder patterns to be considered a match\r
177    */\r
178   private static boolean foundPatternCross(int[] stateCount) {\r
179     int totalModuleSize = 0;\r
180     for (int i = 0; i < 5; i++) {\r
181       int count = stateCount[i];\r
182       if (count == 0) {\r
183         return false;\r
184       }\r
185       totalModuleSize += count;\r
186     }\r
187     if (totalModuleSize < 7) {\r
188       return false;\r
189     }\r
190     int moduleSize = (totalModuleSize << INTEGER_MATH_SHIFT) / 7;\r
191     int maxVariance = moduleSize / 2;\r
192     // Allow less than 50% variance from 1-1-3-1-1 proportions\r
193     return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance &&\r
194         Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance &&\r
195         Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance &&\r
196         Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance &&\r
197         Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;\r
198   }\r
199 \r
200   /**\r
201    * <p>After a horizontal scan finds a potential finder pattern, this method\r
202    * "cross-checks" by scanning down vertically through the center of the possible\r
203    * finder pattern to see if the same proportion is detected.</p>\r
204    *\r
205    * @param startI row where a finder pattern was detected\r
206    * @param centerJ center of the section that appears to cross a finder pattern\r
207    * @param maxCount maximum reasonable number of modules that should be\r
208    * observed in any reading state, based on the results of the horizontal scan\r
209    * @return vertical center of finder pattern, or {@link Float#NaN} if not found\r
210    */\r
211   private float crossCheckVertical(int startI, int centerJ, int maxCount, int originalStateCountTotal) {\r
212     MonochromeBitmapSource image = this.image;\r
213 \r
214     int maxI = image.getHeight();\r
215     int[] stateCount = new int[5];\r
216 \r
217     // Start counting up from center\r
218     int i = startI;\r
219     while (i >= 0 && image.isBlack(centerJ, i)) {\r
220       stateCount[2]++;\r
221       i--;\r
222     }\r
223     if (i < 0) {\r
224       return Float.NaN;\r
225     }\r
226     while (i >= 0 && !image.isBlack(centerJ, i) && stateCount[1] <= maxCount) {\r
227       stateCount[1]++;\r
228       i--;\r
229     }\r
230     // If already too many modules in this state or ran off the edge:\r
231     if (i < 0 || stateCount[1] > maxCount) {\r
232       return Float.NaN;\r
233     }\r
234     while (i >= 0 && image.isBlack(centerJ, i) && stateCount[0] <= maxCount) {\r
235       stateCount[0]++;\r
236       i--;\r
237     }\r
238     if (stateCount[0] > maxCount) {\r
239       return Float.NaN;\r
240     }\r
241 \r
242     // Now also count down from center\r
243     i = startI + 1;\r
244     while (i < maxI && image.isBlack(centerJ, i)) {\r
245       stateCount[2]++;\r
246       i++;\r
247     }\r
248     if (i == maxI) {\r
249       return Float.NaN;\r
250     }\r
251     while (i < maxI && !image.isBlack(centerJ, i) && stateCount[3] < maxCount) {\r
252       stateCount[3]++;\r
253       i++;\r
254     }\r
255     if (i == maxI || stateCount[3] >= maxCount) {\r
256       return Float.NaN;\r
257     }\r
258     while (i < maxI && image.isBlack(centerJ, i) && stateCount[4] < maxCount) {\r
259       stateCount[4]++;\r
260       i++;\r
261     }\r
262     if (stateCount[4] >= maxCount) {\r
263       return Float.NaN;\r
264     }\r
265 \r
266     // If we found a finder-pattern-like section, but its size is more than 20% different than\r
267     // the original, assume it's a false positive\r
268     int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];\r
269     if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {\r
270       return Float.NaN;\r
271     }\r
272 \r
273     return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;\r
274   }\r
275 \r
276   /**\r
277    * <p>Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical,\r
278    * except it reads horizontally instead of vertically. This is used to cross-cross\r
279    * check a vertical cross check and locate the real center of the alignment pattern.</p>\r
280    */\r
281   private float crossCheckHorizontal(int startJ, int centerI, int maxCount, int originalStateCountTotal) {\r
282     MonochromeBitmapSource image = this.image;\r
283 \r
284     int maxJ = image.getWidth();\r
285     int[] stateCount = new int[5];\r
286 \r
287     int j = startJ;\r
288     while (j >= 0 && image.isBlack(j, centerI)) {\r
289       stateCount[2]++;\r
290       j--;\r
291     }\r
292     if (j < 0) {\r
293       return Float.NaN;\r
294     }\r
295     while (j >= 0 && !image.isBlack(j, centerI) && stateCount[1] <= maxCount) {\r
296       stateCount[1]++;\r
297       j--;\r
298     }\r
299     if (j < 0 || stateCount[1] > maxCount) {\r
300       return Float.NaN;\r
301     }\r
302     while (j >= 0 && image.isBlack(j, centerI) && stateCount[0] <= maxCount) {\r
303       stateCount[0]++;\r
304       j--;\r
305     }\r
306     if (stateCount[0] > maxCount) {\r
307       return Float.NaN;\r
308     }\r
309 \r
310     j = startJ + 1;\r
311     while (j < maxJ && image.isBlack(j, centerI)) {\r
312       stateCount[2]++;\r
313       j++;\r
314     }\r
315     if (j == maxJ) {\r
316       return Float.NaN;\r
317     }\r
318     while (j < maxJ && !image.isBlack(j, centerI) && stateCount[3] < maxCount) {\r
319       stateCount[3]++;\r
320       j++;\r
321     }\r
322     if (j == maxJ || stateCount[3] >= maxCount) {\r
323       return Float.NaN;\r
324     }\r
325     while (j < maxJ && image.isBlack(j, centerI) && stateCount[4] < maxCount) {\r
326       stateCount[4]++;\r
327       j++;\r
328     }\r
329     if (stateCount[4] >= maxCount) {\r
330       return Float.NaN;\r
331     }\r
332 \r
333     // If we found a finder-pattern-like section, but its size is significantly different than\r
334     // the original, assume it's a false positive\r
335     int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];\r
336     if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {\r
337       return Float.NaN;\r
338     }\r
339 \r
340     return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN;\r
341   }\r
342 \r
343   /**\r
344    * <p>This is called when a horizontal scan finds a possible alignment pattern. It will\r
345    * cross check with a vertical scan, and if successful, will, ah, cross-cross-check\r
346    * with another horizontal scan. This is needed primarily to locate the real horizontal\r
347    * center of the pattern in cases of extreme skew.</p>\r
348    *\r
349    * <p>If that succeeds the finder pattern location is added to a list that tracks\r
350    * the number of times each location has been nearly-matched as a finder pattern.\r
351    * Each additional find is more evidence that the location is in fact a finder\r
352    * pattern center\r
353    *\r
354    * @param stateCount reading state module counts from horizontal scan\r
355    * @param i row where finder pattern may be found\r
356    * @param j end of possible finder pattern in row\r
357    * @return true if a finder pattern candidate was found this time\r
358    */\r
359   private boolean handlePossibleCenter(int[] stateCount,\r
360                                        int i,\r
361                                        int j) {\r
362     int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];\r
363     float centerJ = centerFromEnd(stateCount, j);\r
364     float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2], stateCountTotal);\r
365     if (!Float.isNaN(centerI)) {\r
366       // Re-cross check\r
367       centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2], stateCountTotal);\r
368       if (!Float.isNaN(centerJ)) {\r
369         float estimatedModuleSize = (float) stateCountTotal / 7.0f;\r
370         boolean found = false;\r
371         int max = possibleCenters.size();\r
372         for (int index = 0; index < max; index++) {\r
373           FinderPattern center = (FinderPattern) possibleCenters.elementAt(index);\r
374           // Look for about the same center and module size:\r
375           if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {\r
376             center.incrementCount();\r
377             found = true;\r
378             break;\r
379           }\r
380         }\r
381         if (!found) {\r
382           possibleCenters.addElement(new FinderPattern(centerJ, centerI, estimatedModuleSize));\r
383         }\r
384         return true;\r
385       }\r
386     }\r
387     return false;\r
388   }\r
389 \r
390   /**\r
391    * @return number of rows we could safely skip during scanning, based on the first\r
392    *         two finder patterns that have been located. In some cases their position will\r
393    *         allow us to infer that the third pattern must lie below a certain point farther\r
394    *         down in the image.\r
395    */\r
396   private int findRowSkip() {\r
397     int max = possibleCenters.size();\r
398     if (max <= 1) {\r
399       return 0;\r
400     }\r
401     FinderPattern firstConfirmedCenter = null;\r
402     for (int i = 0; i < max; i++) {\r
403       FinderPattern center = (FinderPattern) possibleCenters.elementAt(i);\r
404       if (center.getCount() >= CENTER_QUORUM) {\r
405         if (firstConfirmedCenter == null) {\r
406           firstConfirmedCenter = center;\r
407         } else {\r
408           // We have two confirmed centers\r
409           // How far down can we skip before resuming looking for the next\r
410           // pattern? In the worst case, only the difference between the\r
411           // difference in the x / y coordinates of the two centers.\r
412           // This is the case where you find top left first. Draw it out.\r
413           hasSkipped = true;\r
414           return (int) (Math.abs(firstConfirmedCenter.getX() - center.getX()) -\r
415               Math.abs(firstConfirmedCenter.getY() - center.getY()));\r
416         }\r
417       }\r
418     }\r
419     return 0;\r
420   }\r
421 \r
422   /**\r
423    * @return true iff we have found at least 3 finder patterns that have been detected\r
424    *         at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the\r
425    *         candidates is "pretty similar"\r
426    */\r
427   private boolean haveMulitplyConfirmedCenters() {\r
428     int confirmedCount = 0;\r
429     float totalModuleSize = 0.0f;\r
430     int max = possibleCenters.size();\r
431     for (int i = 0; i < max; i++) {\r
432       FinderPattern pattern = (FinderPattern) possibleCenters.elementAt(i);\r
433       if (pattern.getCount() >= CENTER_QUORUM) {\r
434         confirmedCount++;\r
435         totalModuleSize += pattern.getEstimatedModuleSize();\r
436       }\r
437     }\r
438     if (confirmedCount < 3) {\r
439       return false;\r
440     }\r
441     // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"\r
442     // and that we need to keep looking. We detect this by asking if the estimated module sizes\r
443     // vary too much. We arbitrarily say that when the total deviation from average exceeds\r
444     // 15% of the total module size estimates, it's too much.\r
445     float average = totalModuleSize / max;\r
446     float totalDeviation = 0.0f;\r
447     for (int i = 0; i < max; i++) {\r
448       FinderPattern pattern = (FinderPattern) possibleCenters.elementAt(i);\r
449       totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average);\r
450     }\r
451     return totalDeviation <= 0.15f * totalModuleSize;\r
452   }\r
453 \r
454   /**\r
455    * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are\r
456    *         those that have been detected at least {@link #CENTER_QUORUM} times, and whose module\r
457    *         size differs from the average among those patterns the least\r
458    * @throws ReaderException if 3 such finder patterns do not exist\r
459    */\r
460   private FinderPattern[] selectBestPatterns() throws ReaderException {\r
461     Collections.insertionSort(possibleCenters, new CenterComparator());\r
462     int size = 0;\r
463     int max = possibleCenters.size();\r
464     while (size < max) {\r
465       if (((FinderPattern) possibleCenters.elementAt(size)).getCount() < CENTER_QUORUM) {\r
466         break;\r
467       }\r
468       size++;\r
469     }\r
470 \r
471     if (size < 3) {\r
472       // Couldn't find enough finder patterns\r
473       throw new ReaderException("Could not find three finder patterns");\r
474     }\r
475 \r
476     if (size == 3) {\r
477       // Found just enough -- hope these are good!\r
478       return new FinderPattern[]{\r
479           (FinderPattern) possibleCenters.elementAt(0),\r
480           (FinderPattern) possibleCenters.elementAt(1),\r
481           (FinderPattern) possibleCenters.elementAt(2)\r
482       };\r
483     }\r
484 \r
485     possibleCenters.setSize(size);\r
486 \r
487     // Hmm, multiple found. We need to pick the best three. Find the most\r
488     // popular ones whose module size is nearest the average\r
489 \r
490     float averageModuleSize = 0.0f;\r
491     for (int i = 0; i < size; i++) {\r
492       averageModuleSize += ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();\r
493     }\r
494     averageModuleSize /= (float) size;\r
495 \r
496     // We don't have java.util.Collections in J2ME\r
497     Collections.insertionSort(possibleCenters, new ClosestToAverageComparator(averageModuleSize));\r
498 \r
499     return new FinderPattern[]{\r
500         (FinderPattern) possibleCenters.elementAt(0),\r
501         (FinderPattern) possibleCenters.elementAt(1),\r
502         (FinderPattern) possibleCenters.elementAt(2)\r
503     };\r
504   }\r
505 \r
506   /**\r
507    * <p>Orders by {@link FinderPattern#getCount()}, descending.</p>\r
508    */\r
509   private static class CenterComparator implements Comparator {\r
510     public int compare(Object center1, Object center2) {\r
511       return ((FinderPattern) center2).getCount() - ((FinderPattern) center1).getCount();\r
512     }\r
513   }\r
514 \r
515   /**\r
516    * <p>Orders by variance from average module size, ascending.</p>\r
517    */\r
518   private static class ClosestToAverageComparator implements Comparator {\r
519     private final float averageModuleSize;\r
520 \r
521     private ClosestToAverageComparator(float averageModuleSize) {\r
522       this.averageModuleSize = averageModuleSize;\r
523     }\r
524 \r
525     public int compare(Object center1, Object center2) {\r
526       return Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - averageModuleSize) <\r
527           Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - averageModuleSize) ?\r
528           -1 :\r
529           1;\r
530     }\r
531   }\r
532 \r
533 }\r