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