Related to Issue 205, but not the direct issue: read both copies of the format info...
[zxing.git] / core / src / com / google / zxing / qrcode / decoder / BitMatrixParser.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.decoder;\r
18 \r
19 import com.google.zxing.FormatException;\r
20 import com.google.zxing.common.BitMatrix;\r
21 \r
22 /**\r
23  * @author Sean Owen\r
24  */\r
25 final class BitMatrixParser {\r
26 \r
27   private final BitMatrix bitMatrix;\r
28   private Version parsedVersion;\r
29   private FormatInformation parsedFormatInfo;\r
30 \r
31   /**\r
32    * @param bitMatrix {@link BitMatrix} to parse\r
33    * @throws FormatException if dimension is not >= 21 and 1 mod 4\r
34    */\r
35   BitMatrixParser(BitMatrix bitMatrix) throws FormatException {\r
36     int dimension = bitMatrix.getDimension();\r
37     if (dimension < 21 || (dimension & 0x03) != 1) {\r
38       throw FormatException.getFormatInstance();\r
39     }\r
40     this.bitMatrix = bitMatrix;\r
41   }\r
42 \r
43   /**\r
44    * <p>Reads format information from one of its two locations within the QR Code.</p>\r
45    *\r
46    * @return {@link FormatInformation} encapsulating the QR Code's format info\r
47    * @throws FormatException if both format information locations cannot be parsed as\r
48    * the valid encoding of format information\r
49    */\r
50   FormatInformation readFormatInformation() throws FormatException {\r
51 \r
52     if (parsedFormatInfo != null) {\r
53       return parsedFormatInfo;\r
54     }\r
55 \r
56     // Read top-left format info bits\r
57     int formatInfoBits1 = 0;\r
58     for (int i = 0; i < 6; i++) {\r
59       formatInfoBits1 = copyBit(i, 8, formatInfoBits1);\r
60     }\r
61     // .. and skip a bit in the timing pattern ...\r
62     formatInfoBits1 = copyBit(7, 8, formatInfoBits1);\r
63     formatInfoBits1 = copyBit(8, 8, formatInfoBits1);\r
64     formatInfoBits1 = copyBit(8, 7, formatInfoBits1);\r
65     // .. and skip a bit in the timing pattern ...\r
66     for (int j = 5; j >= 0; j--) {\r
67       formatInfoBits1 = copyBit(8, j, formatInfoBits1);\r
68     }\r
69 \r
70     // Read the top-right/bottom-left pattern too\r
71     int dimension = bitMatrix.getDimension();\r
72     int formatInfoBits2 = 0;\r
73     int iMin = dimension - 8;\r
74     for (int i = dimension - 1; i >= iMin; i--) {\r
75       formatInfoBits2 = copyBit(i, 8, formatInfoBits2);\r
76     }\r
77     for (int j = dimension - 7; j < dimension; j++) {\r
78       formatInfoBits2 = copyBit(8, j, formatInfoBits2);\r
79     }\r
80 \r
81     parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2);\r
82     if (parsedFormatInfo != null) {\r
83       return parsedFormatInfo;\r
84     }\r
85     throw FormatException.getFormatInstance();\r
86   }\r
87 \r
88   /**\r
89    * <p>Reads version information from one of its two locations within the QR Code.</p>\r
90    *\r
91    * @return {@link Version} encapsulating the QR Code's version\r
92    * @throws FormatException if both version information locations cannot be parsed as\r
93    * the valid encoding of version information\r
94    */\r
95   Version readVersion() throws FormatException {\r
96 \r
97     if (parsedVersion != null) {\r
98       return parsedVersion;\r
99     }\r
100 \r
101     int dimension = bitMatrix.getDimension();\r
102 \r
103     int provisionalVersion = (dimension - 17) >> 2;\r
104     if (provisionalVersion <= 6) {\r
105       return Version.getVersionForNumber(provisionalVersion);\r
106     }\r
107 \r
108     // Read top-right version info: 3 wide by 6 tall\r
109     int versionBits = 0;\r
110     int ijMin = dimension - 11;\r
111     for (int j = 5; j >= 0; j--) {\r
112       for (int i = dimension - 9; i >= ijMin; i--) {\r
113         versionBits = copyBit(i, j, versionBits);\r
114       }\r
115     }\r
116 \r
117     parsedVersion = Version.decodeVersionInformation(versionBits);\r
118     if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) {\r
119       return parsedVersion;\r
120     }\r
121 \r
122     // Hmm, failed. Try bottom left: 6 wide by 3 tall\r
123     versionBits = 0;\r
124     for (int i = 5; i >= 0; i--) {\r
125       for (int j = dimension - 9; j >= ijMin; j--) {\r
126         versionBits = copyBit(i, j, versionBits);\r
127       }\r
128     }\r
129 \r
130     parsedVersion = Version.decodeVersionInformation(versionBits);\r
131     if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) {\r
132       return parsedVersion;\r
133     }\r
134     throw FormatException.getFormatInstance();\r
135   }\r
136 \r
137   private int copyBit(int i, int j, int versionBits) {\r
138     return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1;\r
139   }\r
140 \r
141   /**\r
142    * <p>Reads the bits in the {@link BitMatrix} representing the finder pattern in the\r
143    * correct order in order to reconstitute the codewords bytes contained within the\r
144    * QR Code.</p>\r
145    *\r
146    * @return bytes encoded within the QR Code\r
147    * @throws FormatException if the exact number of bytes expected is not read\r
148    */\r
149   byte[] readCodewords() throws FormatException {\r
150 \r
151     FormatInformation formatInfo = readFormatInformation();\r
152     Version version = readVersion();\r
153 \r
154     // Get the data mask for the format used in this QR Code. This will exclude\r
155     // some bits from reading as we wind through the bit matrix.\r
156     DataMask dataMask = DataMask.forReference((int) formatInfo.getDataMask());\r
157     int dimension = bitMatrix.getDimension();\r
158     dataMask.unmaskBitMatrix(bitMatrix, dimension);\r
159 \r
160     BitMatrix functionPattern = version.buildFunctionPattern();\r
161 \r
162     boolean readingUp = true;\r
163     byte[] result = new byte[version.getTotalCodewords()];\r
164     int resultOffset = 0;\r
165     int currentByte = 0;\r
166     int bitsRead = 0;\r
167     // Read columns in pairs, from right to left\r
168     for (int j = dimension - 1; j > 0; j -= 2) {\r
169       if (j == 6) {\r
170         // Skip whole column with vertical alignment pattern;\r
171         // saves time and makes the other code proceed more cleanly\r
172         j--;\r
173       }\r
174       // Read alternatingly from bottom to top then top to bottom\r
175       for (int count = 0; count < dimension; count++) {\r
176         int i = readingUp ? dimension - 1 - count : count;\r
177         for (int col = 0; col < 2; col++) {\r
178           // Ignore bits covered by the function pattern\r
179           if (!functionPattern.get(j - col, i)) {\r
180             // Read a bit\r
181             bitsRead++;\r
182             currentByte <<= 1;\r
183             if (bitMatrix.get(j - col, i)) {\r
184               currentByte |= 1;\r
185             }\r
186             // If we've made a whole byte, save it off\r
187             if (bitsRead == 8) {\r
188               result[resultOffset++] = (byte) currentByte;\r
189               bitsRead = 0;\r
190               currentByte = 0;\r
191             }\r
192           }\r
193         }\r
194       }\r
195       readingUp ^= true; // readingUp = !readingUp; // switch directions\r
196     }\r
197     if (resultOffset != version.getTotalCodewords()) {\r
198       throw FormatException.getFormatInstance();\r
199     }\r
200     return result;\r
201   }\r
202 \r
203 }