Bug fix from K. Kakima
[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     float totalModuleSize = 0.0f;\r
150     for (int i = 0; i < patternInfo.length; i++) {\r
151       totalModuleSize += patternInfo[i].getEstimatedModuleSize();\r
152     }\r
153 \r
154     return new FinderPatternInfo(totalModuleSize / (float) patternInfo.length, patternInfo);\r
155   }\r
156 \r
157   /**\r
158    * Given a count of black/white/black/white/black pixels just seen and an end position,\r
159    * figures the location of the center of this run.\r
160    */\r
161   private static float centerFromEnd(int[] stateCount, int end) {\r
162     return (float) (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;\r
163   }\r
164 \r
165   /**\r
166    * @param stateCount count of black/white/black/white/black pixels just read\r
167    * @return true iff the proportions of the counts is close enough to the 1/13/1/1 ratios\r
168    *  used by finder patterns to be considered a match\r
169    */\r
170   private static boolean foundPatternCross(int[] stateCount) {\r
171     int totalModuleSize = 0;\r
172     for (int i = 0; i < 5; i++) {\r
173       if (stateCount[i] == 0) {\r
174         return false;\r
175       }\r
176       totalModuleSize += stateCount[i];\r
177     }\r
178     if (totalModuleSize < 7) {\r
179       return false;\r
180     }\r
181     float moduleSize = (float) totalModuleSize / 7.0f;\r
182     float maxVariance = moduleSize / 2.5f;\r
183     // Allow less than 40% variance from 1-1-3-1-1 proportions\r
184     return  Math.abs(moduleSize - stateCount[0]) < maxVariance &&\r
185             Math.abs(moduleSize - stateCount[1]) < maxVariance &&\r
186             Math.abs(3.0f * moduleSize - stateCount[2]) < 3.0f * maxVariance &&\r
187             Math.abs(moduleSize - stateCount[3]) < maxVariance &&\r
188             Math.abs(moduleSize - stateCount[4]) < maxVariance;\r
189   }\r
190 \r
191   /**\r
192    * <p>After a horizontal scan finds a potential finder pattern, this method\r
193    * "cross-checks" by scanning down vertically through the center of the possible\r
194    * finder pattern to see if the same proportion is detected.</p>\r
195    *\r
196    * @param startI row where a finder pattern was detected\r
197    * @param centerJ center of the section that appears to cross a finder pattern\r
198    * @param maxCount maximum reasonable number of modules that should be\r
199    *  observed in any reading state, based on the results of the horizontal scan\r
200    * @return vertical center of finder pattern, or {@link Float#NaN} if not found\r
201    */\r
202   private float crossCheckVertical(int startI, int centerJ, int maxCount) {\r
203     MonochromeBitmapSource image = this.image;\r
204 \r
205     int maxI = image.getHeight();\r
206     int[] stateCount = new int[5];\r
207 \r
208     // Start counting up from center\r
209     int i = startI;\r
210     while (i >= 0 && image.isBlack(centerJ, i)) {\r
211       stateCount[2]++;\r
212       i--;\r
213     }\r
214     if (i < 0) {\r
215       return Float.NaN;\r
216     }\r
217     while (i >= 0 && !image.isBlack(centerJ, i) && stateCount[1] <= maxCount) {\r
218       stateCount[1]++;\r
219       i--;\r
220     }\r
221     // If already too many modules in this state or ran off the edge:\r
222     if (i < 0 || stateCount[1] > maxCount) {\r
223       return Float.NaN;\r
224     }\r
225     while (i >= 0 && image.isBlack(centerJ, i) && stateCount[0] <= maxCount) {\r
226       stateCount[0]++;\r
227       i--;\r
228     }\r
229     if (i < 0 || stateCount[0] > maxCount) {\r
230       return Float.NaN;\r
231     }\r
232 \r
233     // Now also count down from center\r
234     i = startI + 1;\r
235     while (i < maxI && image.isBlack(centerJ, i)) {\r
236       stateCount[2]++;\r
237       i++;\r
238     }\r
239     if (i == maxI) {\r
240       return Float.NaN;\r
241     }\r
242     while (i < maxI && !image.isBlack(centerJ, i) && stateCount[3] < maxCount) {\r
243       stateCount[3]++;\r
244       i++;\r
245     }\r
246     if (i == maxI || stateCount[3] >= maxCount) {\r
247       return Float.NaN;\r
248     }\r
249     while (i < maxI && image.isBlack(centerJ, i) && stateCount[4] < maxCount) {\r
250       stateCount[4]++;\r
251       i++;\r
252     }\r
253     if (stateCount[4] >= maxCount) {\r
254       return Float.NaN;\r
255     }\r
256 \r
257     return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;\r
258   }\r
259 \r
260   /**\r
261    * <p>Like {@link #crossCheckVertical(int, int, int)}, and in fact is basically identical,\r
262    * except it reads horizontally instead of vertically. This is used to cross-cross\r
263    * check a vertical cross check and locate the real center of the alignment pattern.</p>\r
264    */\r
265   private float crossCheckHorizontal(int startJ, int centerI, int maxCount) {\r
266     MonochromeBitmapSource image = this.image;\r
267 \r
268     int maxJ = image.getWidth();\r
269     int[] stateCount = new int[5];\r
270 \r
271     int j = startJ;\r
272     while (j >= 0 && image.isBlack(j, centerI)) {\r
273       stateCount[2]++;\r
274       j--;\r
275     }\r
276     if (j < 0) {\r
277       return Float.NaN;\r
278     }\r
279     while (j >= 0 && !image.isBlack(j, centerI) && stateCount[1] <= maxCount) {\r
280       stateCount[1]++;\r
281       j--;\r
282     }\r
283     if (j < 0 || stateCount[1] > maxCount) {\r
284       return Float.NaN;\r
285     }\r
286     while (j >= 0 && image.isBlack(j, centerI) && stateCount[0] <= maxCount) {\r
287       stateCount[0]++;\r
288       j--;\r
289     }\r
290     if (j < 0 || stateCount[0] > maxCount) {\r
291       return Float.NaN;\r
292     }\r
293 \r
294     j = startJ + 1;\r
295     while (j < maxJ && image.isBlack(j, centerI)) {\r
296       stateCount[2]++;\r
297       j++;\r
298     }\r
299     if (j == maxJ) {\r
300       return Float.NaN;\r
301     }\r
302     while (j < maxJ && !image.isBlack(j, centerI) && stateCount[3] < maxCount) {\r
303       stateCount[3]++;\r
304       j++;\r
305     }\r
306     if (j == maxJ || stateCount[3] >= maxCount) {\r
307       return Float.NaN;\r
308     }\r
309     while (j < maxJ && image.isBlack(j, centerI) && stateCount[4] < maxCount) {\r
310       stateCount[4]++;\r
311       j++;\r
312     }\r
313     if (stateCount[4] >= maxCount) {\r
314       return Float.NaN;\r
315     }\r
316 \r
317     return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN;\r
318   }\r
319 \r
320   /**\r
321    * <p>This is called when a horizontal scan finds a possible alignment pattern. It will\r
322    * cross check with a vertical scan, and if successful, will, ah, cross-cross-check\r
323    * with another horizontal scan. This is needed primarily to locate the real horizontal\r
324    * center of the pattern in cases of extreme skew.</p>\r
325    *\r
326    * <p>If that succeeds the finder pattern location is added to a list that tracks\r
327    * the number of times each location has been nearly-matched as a finder pattern.\r
328    * Each additional find is more evidence that the location is in fact a finder\r
329    * pattern center\r
330    *\r
331    * @param stateCount reading state module counts from horizontal scan\r
332    * @param i row where finder pattern may be found\r
333    * @param j end of possible finder pattern in row\r
334    * @return true if a finder pattern candidate was found this time\r
335    */\r
336   private boolean handlePossibleCenter(int[] stateCount,\r
337                                        int i,\r
338                                        int j) {\r
339     float centerJ = centerFromEnd(stateCount, j);\r
340     float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2]);\r
341     if (!Float.isNaN(centerI)) {\r
342       // Re-cross check\r
343       centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2]);\r
344       if (!Float.isNaN(centerJ)) {\r
345         float estimatedModuleSize =\r
346           (float) (stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]) / 7.0f;\r
347         boolean found = false;\r
348         int max = possibleCenters.size();\r
349         for (int index = 0; index < max; index++) {\r
350           FinderPattern center = (FinderPattern) possibleCenters.elementAt(index);\r
351           // Look for about the same center and module size:\r
352           if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {\r
353             center.incrementCount();\r
354             found = true;\r
355             break;\r
356           }\r
357         }\r
358         if (!found) {\r
359           possibleCenters.addElement(new FinderPattern(centerJ, centerI, estimatedModuleSize));\r
360         }\r
361         return true;\r
362       }\r
363     }\r
364     return false;\r
365   }\r
366 \r
367   /**\r
368    * @return number of rows we could safely skip during scanning, based on the first\r
369    *  two finder patterns that have been located. In some cases their position will\r
370    *  allow us to infer that the third pattern must lie below a certain point farther\r
371    *  down in the image.\r
372    */\r
373   private int findRowSkip() {\r
374     int max = possibleCenters.size();\r
375     if (max <= 1) {\r
376       return 0;\r
377     }\r
378     FinderPattern firstConfirmedCenter = null;\r
379     for (int i = 0; i < max; i++) {\r
380       FinderPattern center = (FinderPattern) possibleCenters.elementAt(i);\r
381       if (center.getCount() >= CENTER_QUORUM) {\r
382         if (firstConfirmedCenter == null) {\r
383           firstConfirmedCenter = center;\r
384         } else {\r
385           // We have two confirmed centers\r
386           // How far down can we skip before resuming looking for the next\r
387           // pattern? In the worst case, only the difference between the\r
388           // difference in the x / y coordinates of the two centers.\r
389           // This is the case where you find top left first. Draw it out.\r
390           hasSkipped = true;\r
391           return (int) Math.abs(Math.abs(firstConfirmedCenter.getX() - center.getX()) -\r
392                                 Math.abs(firstConfirmedCenter.getY() - center.getY()));\r
393         }\r
394       }\r
395     }\r
396     return 0;\r
397   }\r
398 \r
399   /**\r
400    * @return true iff we have found at least 3 finder patterns that have been detected\r
401    *  at least {@link #CENTER_QUORUM} times each\r
402    */\r
403   private boolean haveMulitplyConfirmedCenters() {\r
404     int count = 0;\r
405     int max = possibleCenters.size();\r
406     for (int i = 0; i < max; i++) {\r
407       if (((FinderPattern) possibleCenters.elementAt(i)).getCount() >= CENTER_QUORUM) {\r
408         if (++count == 3) {\r
409           return true;\r
410         }\r
411       }\r
412     }\r
413     return false;\r
414   }\r
415 \r
416   /**\r
417    * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are\r
418    *  those that have been detected at least {@link #CENTER_QUORUM} times, and whose module\r
419    *  size differs from the average among those patterns the least\r
420    * @throws ReaderException if 3 such finder patterns do not exist\r
421    */\r
422   private FinderPattern[] selectBestPatterns() throws ReaderException {\r
423     Collections.insertionSort(possibleCenters, new CenterComparator());\r
424     int size = 0;\r
425     int max = possibleCenters.size();\r
426     while (size < max) {\r
427       if (((FinderPattern) possibleCenters.elementAt(size)).getCount() < CENTER_QUORUM) {\r
428         break;\r
429       }\r
430       size++;\r
431     }\r
432 \r
433     if (size < 3) {\r
434       // Couldn't find enough finder patterns\r
435       throw new ReaderException("Could not find three finder patterns");\r
436     }\r
437 \r
438     if (size == 3) {\r
439       // Found just enough -- hope these are good!\r
440       return new FinderPattern[] {\r
441         (FinderPattern) possibleCenters.elementAt(0),\r
442         (FinderPattern) possibleCenters.elementAt(1),\r
443         (FinderPattern) possibleCenters.elementAt(2) \r
444       };\r
445     }\r
446 \r
447     possibleCenters.setSize(size);\r
448 \r
449     // Hmm, multiple found. We need to pick the best three. Find the most\r
450     // popular ones whose module size is nearest the average\r
451 \r
452     float averageModuleSize = 0.0f;\r
453     for (int i = 0; i < size; i++) {\r
454       averageModuleSize += ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();\r
455     }\r
456     averageModuleSize /= (float) size;\r
457 \r
458     // We don't have java.util.Collections in J2ME\r
459     Collections.insertionSort(possibleCenters, new ClosestToAverageComparator(averageModuleSize));\r
460 \r
461     return new FinderPattern[] {\r
462       (FinderPattern) possibleCenters.elementAt(0),\r
463       (FinderPattern) possibleCenters.elementAt(1),\r
464       (FinderPattern) possibleCenters.elementAt(2)\r
465     };\r
466   }\r
467 \r
468   /**\r
469    * <p>Having found three "best" finder patterns we need to decide which is the top-left, top-right,\r
470    * bottom-left. We assume that the one closest to the other two is the top-left one; this is not\r
471    * strictly true (imagine extreme perspective distortion) but for the moment is a serviceable assumption.\r
472    * Lastly we sort top-right from bottom-left by figuring out orientation from vector cross products.</p>\r
473    *\r
474    * @param patterns three best {@link FinderPattern}s\r
475    * @return same {@link FinderPattern}s ordered bottom-left, top-left, top-right\r
476    */\r
477   private static FinderPattern[] orderBestPatterns(FinderPattern[] patterns) {\r
478 \r
479     // Find distances between pattern centers\r
480     float abDistance = distance(patterns[0], patterns[1]);\r
481     float bcDistance = distance(patterns[1], patterns[2]);\r
482     float acDistance = distance(patterns[0], patterns[2]);\r
483 \r
484     FinderPattern topLeft;\r
485     FinderPattern topRight;\r
486     FinderPattern bottomLeft;\r
487     // Assume one closest to other two is top left;\r
488     // topRight and bottomLeft will just be guesses below at first\r
489     if (bcDistance >= abDistance && bcDistance >= acDistance) {\r
490       topLeft = patterns[0];\r
491       topRight = patterns[1];\r
492       bottomLeft = patterns[2];\r
493     } else if (acDistance >= bcDistance && acDistance >= abDistance) {\r
494       topLeft = patterns[1];\r
495       topRight = patterns[0];\r
496       bottomLeft = patterns[2];\r
497     } else {\r
498       topLeft = patterns[2];\r
499       topRight = patterns[0];\r
500       bottomLeft = patterns[1];\r
501     }\r
502 \r
503     // Use cross product to figure out which of other1/2 is the bottom left\r
504     // pattern. The vector "top-left -> bottom-left" x "top-left -> top-right"\r
505     // should yield a vector with positive z component\r
506     if ((bottomLeft.getY() - topLeft.getY()) * (topRight.getX() - topLeft.getX()) <\r
507         (bottomLeft.getX() - topLeft.getX()) * (topRight.getY() - topLeft.getY())) {\r
508       FinderPattern temp = topRight;\r
509       topRight = bottomLeft;\r
510       bottomLeft = temp;\r
511     }\r
512 \r
513     return new FinderPattern[] {bottomLeft, topLeft, topRight};\r
514   }\r
515 \r
516   /**\r
517    * @return distance between two points\r
518    */\r
519   static float distance(ResultPoint pattern1, ResultPoint pattern2) {\r
520     float xDiff = pattern1.getX() - pattern2.getX();\r
521     float yDiff = pattern1.getY() - pattern2.getY();\r
522     return (float) Math.sqrt((double) (xDiff * xDiff + yDiff * yDiff));\r
523   }\r
524 \r
525   /**\r
526    * <p>Orders by {@link FinderPattern#getCount()}, descending.</p>\r
527    */\r
528   private static class CenterComparator implements Comparator {\r
529     public int compare(Object center1, Object center2) {\r
530       return ((FinderPattern) center2).getCount() - ((FinderPattern) center1).getCount();\r
531     }\r
532   }\r
533 \r
534   /**\r
535    * <p>Orders by variance from average module size, ascending.</p>\r
536    */\r
537   private static class ClosestToAverageComparator implements Comparator {\r
538     private final float averageModuleSize;\r
539     private ClosestToAverageComparator(float averageModuleSize) {\r
540       this.averageModuleSize = averageModuleSize;\r
541     }\r
542     public int compare(Object center1, Object center2) {\r
543       return Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - averageModuleSize) <\r
544              Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - averageModuleSize) ?\r
545              -1 :\r
546               1;\r
547     }\r
548   }\r
549 \r
550 }\r