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