Small style stuff
[zxing.git] / core / src / com / google / zxing / qrcode / detector / AlignmentPatternFinder.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.NotFoundException;\r
20 import com.google.zxing.ResultPoint;\r
21 import com.google.zxing.ResultPointCallback;\r
22 import com.google.zxing.common.BitMatrix;\r
23 \r
24 import java.util.Vector;\r
25 \r
26 /**\r
27  * <p>This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder\r
28  * patterns but are smaller and appear at regular intervals throughout the image.</p>\r
29  *\r
30  * <p>At the moment this only looks for the bottom-right alignment pattern.</p>\r
31  *\r
32  * <p>This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied,\r
33  * pasted and stripped down here for maximum performance but does unfortunately duplicate\r
34  * some code.</p>\r
35  *\r
36  * <p>This class is thread-safe but not reentrant. Each thread must allocate its own object.\r
37  *\r
38  * @author Sean Owen\r
39  */\r
40 final class AlignmentPatternFinder {\r
41 \r
42   private final BitMatrix image;\r
43   private final Vector possibleCenters;\r
44   private final int startX;\r
45   private final int startY;\r
46   private final int width;\r
47   private final int height;\r
48   private final float moduleSize;\r
49   private final int[] crossCheckStateCount;\r
50   private final ResultPointCallback resultPointCallback;\r
51 \r
52   /**\r
53    * <p>Creates a finder that will look in a portion of the whole image.</p>\r
54    *\r
55    * @param image image to search\r
56    * @param startX left column from which to start searching\r
57    * @param startY top row from which to start searching\r
58    * @param width width of region to search\r
59    * @param height height of region to search\r
60    * @param moduleSize estimated module size so far\r
61    */\r
62   AlignmentPatternFinder(BitMatrix image,\r
63                          int startX,\r
64                          int startY,\r
65                          int width,\r
66                          int height,\r
67                          float moduleSize,\r
68                          ResultPointCallback resultPointCallback) {\r
69     this.image = image;\r
70     this.possibleCenters = new Vector(5);\r
71     this.startX = startX;\r
72     this.startY = startY;\r
73     this.width = width;\r
74     this.height = height;\r
75     this.moduleSize = moduleSize;\r
76     this.crossCheckStateCount = new int[3];\r
77     this.resultPointCallback = resultPointCallback;\r
78   }\r
79 \r
80   /**\r
81    * <p>This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since\r
82    * it's pretty performance-critical and so is written to be fast foremost.</p>\r
83    *\r
84    * @return {@link AlignmentPattern} if found\r
85    * @throws NotFoundException if not found\r
86    */\r
87   AlignmentPattern find() throws NotFoundException {\r
88     int startX = this.startX;\r
89     int height = this.height;\r
90     int maxJ = startX + width;\r
91     int middleI = startY + (height >> 1);\r
92     // We are looking for black/white/black modules in 1:1:1 ratio;\r
93     // this tracks the number of black/white/black modules seen so far\r
94     int[] stateCount = new int[3];\r
95     for (int iGen = 0; iGen < height; iGen++) {\r
96       // Search from middle outwards\r
97       int i = middleI + ((iGen & 0x01) == 0 ? ((iGen + 1) >> 1) : -((iGen + 1) >> 1));\r
98       stateCount[0] = 0;\r
99       stateCount[1] = 0;\r
100       stateCount[2] = 0;\r
101       int j = startX;\r
102       // Burn off leading white pixels before anything else; if we start in the middle of\r
103       // a white run, it doesn't make sense to count its length, since we don't know if the\r
104       // white run continued to the left of the start point\r
105       while (j < maxJ && !image.get(j, i)) {\r
106         j++;\r
107       }\r
108       int currentState = 0;\r
109       while (j < maxJ) {\r
110         if (image.get(j, i)) {\r
111           // Black pixel\r
112           if (currentState == 1) { // Counting black pixels\r
113             stateCount[currentState]++;\r
114           } else { // Counting white pixels\r
115             if (currentState == 2) { // A winner?\r
116               if (foundPatternCross(stateCount)) { // Yes\r
117                 AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, j);\r
118                 if (confirmed != null) {\r
119                   return confirmed;\r
120                 }\r
121               }\r
122               stateCount[0] = stateCount[2];\r
123               stateCount[1] = 1;\r
124               stateCount[2] = 0;\r
125               currentState = 1;\r
126             } else {\r
127               stateCount[++currentState]++;\r
128             }\r
129           }\r
130         } else { // White pixel\r
131           if (currentState == 1) { // Counting black pixels\r
132             currentState++;\r
133           }\r
134           stateCount[currentState]++;\r
135         }\r
136         j++;\r
137       }\r
138       if (foundPatternCross(stateCount)) {\r
139         AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, maxJ);\r
140         if (confirmed != null) {\r
141           return confirmed;\r
142         }\r
143       }\r
144 \r
145     }\r
146 \r
147     // Hmm, nothing we saw was observed and confirmed twice. If we had\r
148     // any guess at all, return it.\r
149     if (!possibleCenters.isEmpty()) {\r
150       return (AlignmentPattern) possibleCenters.elementAt(0);\r
151     }\r
152 \r
153     throw NotFoundException.getNotFoundInstance();\r
154   }\r
155 \r
156   /**\r
157    * Given a count of black/white/black pixels just seen and an end position,\r
158    * figures the location of the center of this black/white/black run.\r
159    */\r
160   private static float centerFromEnd(int[] stateCount, int end) {\r
161     return (float) (end - stateCount[2]) - stateCount[1] / 2.0f;\r
162   }\r
163 \r
164   /**\r
165    * @param stateCount count of black/white/black pixels just read\r
166    * @return true iff the proportions of the counts is close enough to the 1/1/1 ratios\r
167    *         used by alignment patterns to be considered a match\r
168    */\r
169   private boolean foundPatternCross(int[] stateCount) {\r
170     float moduleSize = this.moduleSize;\r
171     float maxVariance = moduleSize / 2.0f;\r
172     for (int i = 0; i < 3; i++) {\r
173       if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) {\r
174         return false;\r
175       }\r
176     }\r
177     return true;\r
178   }\r
179 \r
180   /**\r
181    * <p>After a horizontal scan finds a potential alignment pattern, this method\r
182    * "cross-checks" by scanning down vertically through the center of the possible\r
183    * alignment pattern to see if the same proportion is detected.</p>\r
184    *\r
185    * @param startI row where an alignment pattern was detected\r
186    * @param centerJ center of the section that appears to cross an alignment pattern\r
187    * @param maxCount maximum reasonable number of modules that should be\r
188    * observed in any reading state, based on the results of the horizontal scan\r
189    * @return vertical center of alignment pattern, or {@link Float#NaN} if not found\r
190    */\r
191   private float crossCheckVertical(int startI, int centerJ, int maxCount,\r
192       int originalStateCountTotal) {\r
193     BitMatrix image = this.image;\r
194 \r
195     int maxI = image.getHeight();\r
196     int[] stateCount = crossCheckStateCount;\r
197     stateCount[0] = 0;\r
198     stateCount[1] = 0;\r
199     stateCount[2] = 0;\r
200 \r
201     // Start counting up from center\r
202     int i = startI;\r
203     while (i >= 0 && image.get(centerJ, i) && stateCount[1] <= maxCount) {\r
204       stateCount[1]++;\r
205       i--;\r
206     }\r
207     // If already too many modules in this state or ran off the edge:\r
208     if (i < 0 || stateCount[1] > maxCount) {\r
209       return Float.NaN;\r
210     }\r
211     while (i >= 0 && !image.get(centerJ, i) && stateCount[0] <= maxCount) {\r
212       stateCount[0]++;\r
213       i--;\r
214     }\r
215     if (stateCount[0] > maxCount) {\r
216       return Float.NaN;\r
217     }\r
218 \r
219     // Now also count down from center\r
220     i = startI + 1;\r
221     while (i < maxI && image.get(centerJ, i) && stateCount[1] <= maxCount) {\r
222       stateCount[1]++;\r
223       i++;\r
224     }\r
225     if (i == maxI || stateCount[1] > maxCount) {\r
226       return Float.NaN;\r
227     }\r
228     while (i < maxI && !image.get(centerJ, i) && stateCount[2] <= maxCount) {\r
229       stateCount[2]++;\r
230       i++;\r
231     }\r
232     if (stateCount[2] > maxCount) {\r
233       return Float.NaN;\r
234     }\r
235 \r
236     int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];\r
237     if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {\r
238       return Float.NaN;\r
239     }\r
240 \r
241     return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;\r
242   }\r
243 \r
244   /**\r
245    * <p>This is called when a horizontal scan finds a possible alignment pattern. It will\r
246    * cross check with a vertical scan, and if successful, will see if this pattern had been\r
247    * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have\r
248    * found the alignment pattern.</p>\r
249    *\r
250    * @param stateCount reading state module counts from horizontal scan\r
251    * @param i row where alignment pattern may be found\r
252    * @param j end of possible alignment pattern in row\r
253    * @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not\r
254    */\r
255   private AlignmentPattern handlePossibleCenter(int[] stateCount, int i, int j) {\r
256     int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];\r
257     float centerJ = centerFromEnd(stateCount, j);\r
258     float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1], stateCountTotal);\r
259     if (!Float.isNaN(centerI)) {\r
260       float estimatedModuleSize = (float) (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f;\r
261       int max = possibleCenters.size();\r
262       for (int index = 0; index < max; index++) {\r
263         AlignmentPattern center = (AlignmentPattern) possibleCenters.elementAt(index);\r
264         // Look for about the same center and module size:\r
265         if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {\r
266           return new AlignmentPattern(centerJ, centerI, estimatedModuleSize);\r
267         }\r
268       }\r
269       // Hadn't found this before; save it\r
270       ResultPoint point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);\r
271       possibleCenters.addElement(point);\r
272       if (resultPointCallback != null) {\r
273         resultPointCallback.foundPossibleResultPoint(point);\r
274       }\r
275     }\r
276     return null;\r
277   }\r
278 \r
279 }\r