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