"Split" ReaderException into subclasses to enable more useful error reporting
[zxing.git] / core / src / com / google / zxing / qrcode / decoder / Decoder.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.ChecksumException;\r
20 import com.google.zxing.FormatException;\r
21 import com.google.zxing.NotFoundException;\r
22 import com.google.zxing.common.BitMatrix;\r
23 import com.google.zxing.common.DecoderResult;\r
24 import com.google.zxing.common.reedsolomon.GF256;\r
25 import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;\r
26 import com.google.zxing.common.reedsolomon.ReedSolomonException;\r
27 \r
28 import java.util.Hashtable;\r
29 \r
30 /**\r
31  * <p>The main class which implements QR Code decoding -- as opposed to locating and extracting\r
32  * the QR Code from an image.</p>\r
33  *\r
34  * @author Sean Owen\r
35  */\r
36 public final class Decoder {\r
37 \r
38   private final ReedSolomonDecoder rsDecoder;\r
39 \r
40   public Decoder() {\r
41     rsDecoder = new ReedSolomonDecoder(GF256.QR_CODE_FIELD);\r
42   }\r
43 \r
44   public DecoderResult decode(boolean[][] image)\r
45       throws ChecksumException, FormatException, NotFoundException {\r
46     return decode(image, null);\r
47   }\r
48 \r
49   /**\r
50    * <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.\r
51    * "true" is taken to mean a black module.</p>\r
52    *\r
53    * @param image booleans representing white/black QR Code modules\r
54    * @return text and bytes encoded within the QR Code\r
55    * @throws NotFoundException if the QR Code cannot be found\r
56    * @throws FormatException if the QR Code cannot be decoded\r
57    * @throws ChecksumException if error correction fails\r
58    */\r
59   public DecoderResult decode(boolean[][] image, Hashtable hints)\r
60       throws ChecksumException, FormatException, NotFoundException {\r
61     int dimension = image.length;\r
62     BitMatrix bits = new BitMatrix(dimension);\r
63     for (int i = 0; i < dimension; i++) {\r
64       for (int j = 0; j < dimension; j++) {\r
65         if (image[i][j]) {\r
66           bits.set(j, i);\r
67         }\r
68       }\r
69     }\r
70     return decode(bits, hints);\r
71   }\r
72 \r
73   public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException, NotFoundException {\r
74     return decode(bits, null);\r
75   }\r
76 \r
77   /**\r
78    * <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>\r
79    *\r
80    * @param bits booleans representing white/black QR Code modules\r
81    * @return text and bytes encoded within the QR Code\r
82    * @throws NotFoundException if the QR Code cannot be found\r
83    * @throws FormatException if the QR Code cannot be decoded\r
84    * @throws ChecksumException if error correction fails\r
85    */\r
86   public DecoderResult decode(BitMatrix bits, Hashtable hints)\r
87       throws NotFoundException, FormatException, ChecksumException {\r
88 \r
89     // Construct a parser and read version, error-correction level\r
90     BitMatrixParser parser = new BitMatrixParser(bits);\r
91     Version version = parser.readVersion();\r
92     ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();\r
93 \r
94     // Read codewords\r
95     byte[] codewords = parser.readCodewords();\r
96     // Separate into data blocks\r
97     DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);\r
98 \r
99     // Count total number of data bytes\r
100     int totalBytes = 0;\r
101     for (int i = 0; i < dataBlocks.length; i++) {\r
102       totalBytes += dataBlocks[i].getNumDataCodewords();\r
103     }\r
104     byte[] resultBytes = new byte[totalBytes];\r
105     int resultOffset = 0;\r
106 \r
107     // Error-correct and copy data blocks together into a stream of bytes\r
108     for (int j = 0; j < dataBlocks.length; j++) {\r
109       DataBlock dataBlock = dataBlocks[j];\r
110       byte[] codewordBytes = dataBlock.getCodewords();\r
111       int numDataCodewords = dataBlock.getNumDataCodewords();\r
112       correctErrors(codewordBytes, numDataCodewords);\r
113       for (int i = 0; i < numDataCodewords; i++) {\r
114         resultBytes[resultOffset++] = codewordBytes[i];\r
115       }\r
116     }\r
117 \r
118     // Decode the contents of that stream of bytes\r
119     return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);\r
120   }\r
121 \r
122   /**\r
123    * <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to\r
124    * correct the errors in-place using Reed-Solomon error correction.</p>\r
125    *\r
126    * @param codewordBytes data and error correction codewords\r
127    * @param numDataCodewords number of codewords that are data bytes\r
128    * @throws ChecksumException if error correction fails\r
129    */\r
130   private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {\r
131     int numCodewords = codewordBytes.length;\r
132     // First read into an array of ints\r
133     int[] codewordsInts = new int[numCodewords];\r
134     for (int i = 0; i < numCodewords; i++) {\r
135       codewordsInts[i] = codewordBytes[i] & 0xFF;\r
136     }\r
137     int numECCodewords = codewordBytes.length - numDataCodewords;\r
138     try {\r
139       rsDecoder.decode(codewordsInts, numECCodewords);\r
140     } catch (ReedSolomonException rse) {\r
141       throw ChecksumException.getChecksumInstance();\r
142     }\r
143     // Copy back into array of bytes -- only need to worry about the bytes that were data\r
144     // We don't care about errors in the error-correction codewords\r
145     for (int i = 0; i < numDataCodewords; i++) {\r
146       codewordBytes[i] = (byte) codewordsInts[i];\r
147     }\r
148   }\r
149 \r
150 }\r