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