• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2010 ZXing authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.zxing.aztec.decoder;
18 
19 import com.google.zxing.FormatException;
20 import com.google.zxing.aztec.AztecDetectorResult;
21 import com.google.zxing.common.BitMatrix;
22 import com.google.zxing.common.CharacterSetECI;
23 import com.google.zxing.common.DecoderResult;
24 import com.google.zxing.common.reedsolomon.GenericGF;
25 import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
26 import com.google.zxing.common.reedsolomon.ReedSolomonException;
27 
28 import java.io.ByteArrayOutputStream;
29 import java.io.UnsupportedEncodingException;
30 import java.nio.charset.Charset;
31 import java.nio.charset.StandardCharsets;
32 import java.util.Arrays;
33 
34 /**
35  * <p>The main class which implements Aztec Code decoding -- as opposed to locating and extracting
36  * the Aztec Code from an image.</p>
37  *
38  * @author David Olivier
39  */
40 public final class Decoder {
41 
42   private enum Table {
43     UPPER,
44     LOWER,
45     MIXED,
46     DIGIT,
47     PUNCT,
48     BINARY
49   }
50 
51   private static final String[] UPPER_TABLE = {
52       "CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
53       "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
54   };
55 
56   private static final String[] LOWER_TABLE = {
57       "CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
58       "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
59   };
60 
61   private static final String[] MIXED_TABLE = {
62       "CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
63       "\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
64       "`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
65   };
66 
67   private static final String[] PUNCT_TABLE = {
68       "FLG(n)", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
69       "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
70   };
71 
72   private static final String[] DIGIT_TABLE = {
73       "CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
74   };
75 
76   private static final Charset DEFAULT_ENCODING = StandardCharsets.ISO_8859_1;
77 
78   private AztecDetectorResult ddata;
79 
decode(AztecDetectorResult detectorResult)80   public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException {
81     ddata = detectorResult;
82     BitMatrix matrix = detectorResult.getBits();
83     boolean[] rawbits = extractBits(matrix);
84     CorrectedBitsResult correctedBits = correctBits(rawbits);
85     byte[] rawBytes = convertBoolArrayToByteArray(correctedBits.correctBits);
86     String result = getEncodedData(correctedBits.correctBits);
87     DecoderResult decoderResult =
88         new DecoderResult(rawBytes, result, null, String.format("%d%%", correctedBits.ecLevel));
89     decoderResult.setNumBits(correctedBits.correctBits.length);
90     decoderResult.setErrorsCorrected(correctedBits.errorsCorrected);
91     return decoderResult;
92   }
93 
94   // This method is used for testing the high-level encoder
highLevelDecode(boolean[] correctedBits)95   public static String highLevelDecode(boolean[] correctedBits) throws FormatException {
96     return getEncodedData(correctedBits);
97   }
98 
99   /**
100    * Gets the string encoded in the aztec code bits
101    *
102    * @return the decoded string
103    */
getEncodedData(boolean[] correctedBits)104   private static String getEncodedData(boolean[] correctedBits) throws FormatException {
105     int endIndex = correctedBits.length;
106     Table latchTable = Table.UPPER; // table most recently latched to
107     Table shiftTable = Table.UPPER; // table to use for the next read
108 
109     // Final decoded string result
110     // (correctedBits-5) / 4 is an upper bound on the size (all-digit result)
111     StringBuilder result = new StringBuilder((correctedBits.length - 5) / 4);
112 
113     // Intermediary buffer of decoded bytes, which is decoded into a string and flushed
114     // when character encoding changes (ECI) or input ends.
115     ByteArrayOutputStream decodedBytes = new ByteArrayOutputStream();
116     Charset encoding = DEFAULT_ENCODING;
117 
118     int index = 0;
119     while (index < endIndex) {
120       if (shiftTable == Table.BINARY) {
121         if (endIndex - index < 5) {
122           break;
123         }
124         int length = readCode(correctedBits, index, 5);
125         index += 5;
126         if (length == 0) {
127           if (endIndex - index < 11) {
128             break;
129           }
130           length = readCode(correctedBits, index, 11) + 31;
131           index += 11;
132         }
133         for (int charCount = 0; charCount < length; charCount++) {
134           if (endIndex - index < 8) {
135             index = endIndex;  // Force outer loop to exit
136             break;
137           }
138           int code = readCode(correctedBits, index, 8);
139           decodedBytes.write((byte) code);
140           index += 8;
141         }
142         // Go back to whatever mode we had been in
143         shiftTable = latchTable;
144       } else {
145         int size = shiftTable == Table.DIGIT ? 4 : 5;
146         if (endIndex - index < size) {
147           break;
148         }
149         int code = readCode(correctedBits, index, size);
150         index += size;
151         String str = getCharacter(shiftTable, code);
152         if ("FLG(n)".equals(str)) {
153           if (endIndex - index < 3) {
154             break;
155           }
156           int n = readCode(correctedBits, index, 3);
157           index += 3;
158           //  flush bytes, FLG changes state
159           try {
160             result.append(decodedBytes.toString(encoding.name()));
161           } catch (UnsupportedEncodingException uee) {
162             throw new IllegalStateException(uee);
163           }
164           decodedBytes.reset();
165           switch (n) {
166             case 0:
167               result.append((char) 29);  // translate FNC1 as ASCII 29
168               break;
169             case 7:
170               throw FormatException.getFormatInstance(); // FLG(7) is reserved and illegal
171             default:
172               // ECI is decimal integer encoded as 1-6 codes in DIGIT mode
173               int eci = 0;
174               if (endIndex - index < 4 * n) {
175                 break;
176               }
177               while (n-- > 0) {
178                 int nextDigit = readCode(correctedBits, index, 4);
179                 index += 4;
180                 if (nextDigit < 2 || nextDigit > 11) {
181                   throw FormatException.getFormatInstance(); // Not a decimal digit
182                 }
183                 eci = eci * 10 + (nextDigit - 2);
184               }
185               CharacterSetECI charsetECI = CharacterSetECI.getCharacterSetECIByValue(eci);
186               if (charsetECI == null) {
187                 throw FormatException.getFormatInstance();
188               }
189               encoding = charsetECI.getCharset();
190           }
191           // Go back to whatever mode we had been in
192           shiftTable = latchTable;
193         } else if (str.startsWith("CTRL_")) {
194           // Table changes
195           // ISO/IEC 24778:2008 prescribes ending a shift sequence in the mode from which it was invoked.
196           // That's including when that mode is a shift.
197           // Our test case dlusbs.png for issue #642 exercises that.
198           latchTable = shiftTable;  // Latch the current mode, so as to return to Upper after U/S B/S
199           shiftTable = getTable(str.charAt(5));
200           if (str.charAt(6) == 'L') {
201             latchTable = shiftTable;
202           }
203         } else {
204           // Though stored as a table of strings for convenience, codes actually represent 1 or 2 *bytes*.
205           byte[] b = str.getBytes(StandardCharsets.US_ASCII);
206           decodedBytes.write(b, 0, b.length);
207           // Go back to whatever mode we had been in
208           shiftTable = latchTable;
209         }
210       }
211     }
212     try {
213       result.append(decodedBytes.toString(encoding.name()));
214     } catch (UnsupportedEncodingException uee) {
215       // can't happen
216       throw new IllegalStateException(uee);
217     }
218     return result.toString();
219   }
220 
221   /**
222    * gets the table corresponding to the char passed
223    */
getTable(char t)224   private static Table getTable(char t) {
225     switch (t) {
226       case 'L':
227         return Table.LOWER;
228       case 'P':
229         return Table.PUNCT;
230       case 'M':
231         return Table.MIXED;
232       case 'D':
233         return Table.DIGIT;
234       case 'B':
235         return Table.BINARY;
236       case 'U':
237       default:
238         return Table.UPPER;
239     }
240   }
241 
242   /**
243    * Gets the character (or string) corresponding to the passed code in the given table
244    *
245    * @param table the table used
246    * @param code the code of the character
247    */
getCharacter(Table table, int code)248   private static String getCharacter(Table table, int code) {
249     switch (table) {
250       case UPPER:
251         return UPPER_TABLE[code];
252       case LOWER:
253         return LOWER_TABLE[code];
254       case MIXED:
255         return MIXED_TABLE[code];
256       case PUNCT:
257         return PUNCT_TABLE[code];
258       case DIGIT:
259         return DIGIT_TABLE[code];
260       default:
261         // Should not reach here.
262         throw new IllegalStateException("Bad table");
263     }
264   }
265 
266   static final class CorrectedBitsResult {
267     private final boolean[] correctBits;
268     private final int errorsCorrected;
269     private final int ecLevel;
270 
CorrectedBitsResult(boolean[] correctBits, int errorsCorrected, int ecLevel)271     CorrectedBitsResult(boolean[] correctBits, int errorsCorrected, int ecLevel) {
272       this.correctBits = correctBits;
273       this.errorsCorrected = errorsCorrected;
274       this.ecLevel = ecLevel;
275     }
276   }
277 
278   /**
279    * <p>Performs RS error correction on an array of bits.</p>
280    *
281    * @return the corrected array
282    * @throws FormatException if the input contains too many errors
283    */
correctBits(boolean[] rawbits)284   private CorrectedBitsResult correctBits(boolean[] rawbits) throws FormatException {
285     GenericGF gf;
286     int codewordSize;
287 
288     if (ddata.getNbLayers() <= 2) {
289       codewordSize = 6;
290       gf = GenericGF.AZTEC_DATA_6;
291     } else if (ddata.getNbLayers() <= 8) {
292       codewordSize = 8;
293       gf = GenericGF.AZTEC_DATA_8;
294     } else if (ddata.getNbLayers() <= 22) {
295       codewordSize = 10;
296       gf = GenericGF.AZTEC_DATA_10;
297     } else {
298       codewordSize = 12;
299       gf = GenericGF.AZTEC_DATA_12;
300     }
301 
302     int numDataCodewords = ddata.getNbDatablocks();
303     int numCodewords = rawbits.length / codewordSize;
304     if (numCodewords < numDataCodewords) {
305       throw FormatException.getFormatInstance();
306     }
307     int offset = rawbits.length % codewordSize;
308 
309     int[] dataWords = new int[numCodewords];
310     for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
311       dataWords[i] = readCode(rawbits, offset, codewordSize);
312     }
313 
314     int errorsCorrected = 0;
315     try {
316       ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
317       errorsCorrected = rsDecoder.decodeWithECCount(dataWords, numCodewords - numDataCodewords);
318     } catch (ReedSolomonException ex) {
319       throw FormatException.getFormatInstance(ex);
320     }
321 
322     // Now perform the unstuffing operation.
323     // First, count how many bits are going to be thrown out as stuffing
324     int mask = (1 << codewordSize) - 1;
325     int stuffedBits = 0;
326     for (int i = 0; i < numDataCodewords; i++) {
327       int dataWord = dataWords[i];
328       if (dataWord == 0 || dataWord == mask) {
329         throw FormatException.getFormatInstance();
330       } else if (dataWord == 1 || dataWord == mask - 1) {
331         stuffedBits++;
332       }
333     }
334     // Now, actually unpack the bits and remove the stuffing
335     boolean[] correctedBits = new boolean[numDataCodewords * codewordSize - stuffedBits];
336     int index = 0;
337     for (int i = 0; i < numDataCodewords; i++) {
338       int dataWord = dataWords[i];
339       if (dataWord == 1 || dataWord == mask - 1) {
340         // next codewordSize-1 bits are all zeros or all ones
341         Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
342         index += codewordSize - 1;
343       } else {
344         for (int bit = codewordSize - 1; bit >= 0; --bit) {
345           correctedBits[index++] = (dataWord & (1 << bit)) != 0;
346         }
347       }
348     }
349 
350     int ecLevel = 100 * (numCodewords - numDataCodewords) / numCodewords;
351     return new CorrectedBitsResult(correctedBits, errorsCorrected, ecLevel);
352   }
353 
354   /**
355    * Gets the array of bits from an Aztec Code matrix
356    *
357    * @return the array of bits
358    */
extractBits(BitMatrix matrix)359   private boolean[] extractBits(BitMatrix matrix) {
360     boolean compact = ddata.isCompact();
361     int layers = ddata.getNbLayers();
362     int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
363     int[] alignmentMap = new int[baseMatrixSize];
364     boolean[] rawbits = new boolean[totalBitsInLayer(layers, compact)];
365 
366     if (compact) {
367       for (int i = 0; i < alignmentMap.length; i++) {
368         alignmentMap[i] = i;
369       }
370     } else {
371       int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
372       int origCenter = baseMatrixSize / 2;
373       int center = matrixSize / 2;
374       for (int i = 0; i < origCenter; i++) {
375         int newOffset = i + i / 15;
376         alignmentMap[origCenter - i - 1] = center - newOffset - 1;
377         alignmentMap[origCenter + i] = center + newOffset + 1;
378       }
379     }
380     for (int i = 0, rowOffset = 0; i < layers; i++) {
381       int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
382       // The top-left most point of this layer is <low, low> (not including alignment lines)
383       int low = i * 2;
384       // The bottom-right most point of this layer is <high, high> (not including alignment lines)
385       int high = baseMatrixSize - 1 - low;
386       // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
387       for (int j = 0; j < rowSize; j++) {
388         int columnOffset = j * 2;
389         for (int k = 0; k < 2; k++) {
390           // left column
391           rawbits[rowOffset + columnOffset + k] =
392               matrix.get(alignmentMap[low + k], alignmentMap[low + j]);
393           // bottom row
394           rawbits[rowOffset + 2 * rowSize + columnOffset + k] =
395               matrix.get(alignmentMap[low + j], alignmentMap[high - k]);
396           // right column
397           rawbits[rowOffset + 4 * rowSize + columnOffset + k] =
398               matrix.get(alignmentMap[high - k], alignmentMap[high - j]);
399           // top row
400           rawbits[rowOffset + 6 * rowSize + columnOffset + k] =
401               matrix.get(alignmentMap[high - j], alignmentMap[low + k]);
402         }
403       }
404       rowOffset += rowSize * 8;
405     }
406     return rawbits;
407   }
408 
409   /**
410    * Reads a code of given length and at given index in an array of bits
411    */
readCode(boolean[] rawbits, int startIndex, int length)412   private static int readCode(boolean[] rawbits, int startIndex, int length) {
413     int res = 0;
414     for (int i = startIndex; i < startIndex + length; i++) {
415       res <<= 1;
416       if (rawbits[i]) {
417         res |= 0x01;
418       }
419     }
420     return res;
421   }
422 
423   /**
424    * Reads a code of length 8 in an array of bits, padding with zeros
425    */
readByte(boolean[] rawbits, int startIndex)426   private static byte readByte(boolean[] rawbits, int startIndex) {
427     int n = rawbits.length - startIndex;
428     if (n >= 8) {
429       return (byte) readCode(rawbits, startIndex, 8);
430     }
431     return (byte) (readCode(rawbits, startIndex, n) << (8 - n));
432   }
433 
434   /**
435    * Packs a bit array into bytes, most significant bit first
436    */
convertBoolArrayToByteArray(boolean[] boolArr)437   static byte[] convertBoolArrayToByteArray(boolean[] boolArr) {
438     byte[] byteArr = new byte[(boolArr.length + 7) / 8];
439     for (int i = 0; i < byteArr.length; i++) {
440       byteArr[i] = readByte(boolArr, 8 * i);
441     }
442     return byteArr;
443   }
444 
totalBitsInLayer(int layers, boolean compact)445   private static int totalBitsInLayer(int layers, boolean compact) {
446     return ((compact ? 88 : 112) + 16 * layers) * layers;
447   }
448 }
449