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