/* * Copyright 2010 ZXing authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Text; using ZXing.Common; using ZXing.Common.ReedSolomon; namespace ZXing.Aztec.Internal { /// /// The main class which implements Aztec Code decoding -- as opposed to locating and extracting /// the Aztec Code from an image. /// /// David Olivier public sealed class Decoder { private enum Table { UPPER, LOWER, MIXED, DIGIT, PUNCT, BINARY } private static readonly String[] UPPER_TABLE = { "CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS" }; private static readonly String[] LOWER_TABLE = { "CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS" }; private static readonly String[] MIXED_TABLE = { "CTRL_PS", " ", "\x1", "\x2", "\x3", "\x4", "\x5", "\x6", "\x7", "\b", "\t", "\n", "\xD", "\f", "\r", "\x21", "\x22", "\x23", "\x24", "\x25", "@", "\\", "^", "_", "`", "|", "~", "\xB1", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS" }; private static readonly String[] PUNCT_TABLE = { "", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL" }; private static readonly String[] DIGIT_TABLE = { "CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US" }; private static readonly IDictionary codeTables = new Dictionary { {Table.UPPER, UPPER_TABLE}, {Table.LOWER, LOWER_TABLE}, {Table.MIXED, MIXED_TABLE}, {Table.PUNCT, PUNCT_TABLE}, {Table.DIGIT, DIGIT_TABLE}, {Table.BINARY, null} }; private static readonly IDictionary codeTableMap = new Dictionary { {'U', Table.UPPER}, {'L', Table.LOWER}, {'M', Table.MIXED}, {'P', Table.PUNCT}, {'D', Table.DIGIT}, {'B', Table.BINARY} }; private AztecDetectorResult ddata; /// /// Decodes the specified detector result. /// /// The detector result. /// public DecoderResult decode(AztecDetectorResult detectorResult) { ddata = detectorResult; BitMatrix matrix = detectorResult.Bits; bool[] rawbits = extractBits(matrix); if (rawbits == null) return null; bool[] correctedBits = correctBits(rawbits); if (correctedBits == null) return null; String result = getEncodedData(correctedBits); if (result == null) return null; return new DecoderResult(null, result, null, null); } // This method is used for testing the high-level encoder public static String highLevelDecode(bool[] correctedBits) { return getEncodedData(correctedBits); } /// /// Gets the string encoded in the aztec code bits /// /// The corrected bits. /// the decoded string private static String getEncodedData(bool[] correctedBits) { var endIndex = correctedBits.Length; var latchTable = Table.UPPER; // table most recently latched to var shiftTable = Table.UPPER; // table to use for the next read var strTable = UPPER_TABLE; var result = new StringBuilder(20); var index = 0; while (index < endIndex) { if (shiftTable == Table.BINARY) { if (endIndex - index < 5) { break; } int length = readCode(correctedBits, index, 5); index += 5; if (length == 0) { if (endIndex - index < 11) { break; } length = readCode(correctedBits, index, 11) + 31; index += 11; } for (int charCount = 0; charCount < length; charCount++) { if (endIndex - index < 8) { index = endIndex; // Force outer loop to exit break; } int code = readCode(correctedBits, index, 8); result.Append((char) code); index += 8; } // Go back to whatever mode we had been in shiftTable = latchTable; strTable = codeTables[shiftTable]; } else { int size = shiftTable == Table.DIGIT ? 4 : 5; if (endIndex - index < size) { break; } int code = readCode(correctedBits, index, size); index += size; String str = getCharacter(strTable, code); if (str.StartsWith("CTRL_")) { // Table changes shiftTable = getTable(str[5]); strTable = codeTables[shiftTable]; if (str[6] == 'L') { latchTable = shiftTable; } } else { result.Append(str); // Go back to whatever mode we had been in shiftTable = latchTable; strTable = codeTables[shiftTable]; } } } return result.ToString(); } /// /// gets the table corresponding to the char passed /// /// The t. /// private static Table getTable(char t) { if (!codeTableMap.ContainsKey(t)) return codeTableMap['U']; return codeTableMap[t]; } /// /// Gets the character (or string) corresponding to the passed code in the given table /// /// the table used /// the code of the character /// private static String getCharacter(String[] table, int code) { return table[code]; } /// ///Performs RS error correction on an array of bits. /// /// The rawbits. /// the corrected array private bool[] correctBits(bool[] rawbits) { GenericGF gf; int codewordSize; if (ddata.NbLayers <= 2) { codewordSize = 6; gf = GenericGF.AZTEC_DATA_6; } else if (ddata.NbLayers <= 8) { codewordSize = 8; gf = GenericGF.AZTEC_DATA_8; } else if (ddata.NbLayers <= 22) { codewordSize = 10; gf = GenericGF.AZTEC_DATA_10; } else { codewordSize = 12; gf = GenericGF.AZTEC_DATA_12; } int numDataCodewords = ddata.NbDatablocks; int numCodewords = rawbits.Length/codewordSize; if (numCodewords < numDataCodewords) return null; int offset = rawbits.Length % codewordSize; int numECCodewords = numCodewords - numDataCodewords; int[] dataWords = new int[numCodewords]; for (int i = 0; i < numCodewords; i++, offset += codewordSize) { dataWords[i] = readCode(rawbits, offset, codewordSize); } var rsDecoder = new ReedSolomonDecoder(gf); if (!rsDecoder.decode(dataWords, numECCodewords)) return null; // Now perform the unstuffing operation. // First, count how many bits are going to be thrown out as stuffing int mask = (1 << codewordSize) - 1; int stuffedBits = 0; for (int i = 0; i < numDataCodewords; i++) { int dataWord = dataWords[i]; if (dataWord == 0 || dataWord == mask) { return null; } else if (dataWord == 1 || dataWord == mask - 1) { stuffedBits++; } } // Now, actually unpack the bits and remove the stuffing bool[] correctedBits = new bool[numDataCodewords*codewordSize - stuffedBits]; int index = 0; for (int i = 0; i < numDataCodewords; i++) { int dataWord = dataWords[i]; if (dataWord == 1 || dataWord == mask - 1) { // next codewordSize-1 bits are all zeros or all ones SupportClass.Fill(correctedBits, index, index + codewordSize - 1, dataWord > 1); index += codewordSize - 1; } else { for (int bit = codewordSize - 1; bit >= 0; --bit) { correctedBits[index++] = (dataWord & (1 << bit)) != 0; } } } if (index != correctedBits.Length) return null; return correctedBits; } /// /// Gets the array of bits from an Aztec Code matrix /// /// The matrix. /// the array of bits private bool[] extractBits(BitMatrix matrix) { bool compact = ddata.Compact; int layers = ddata.NbLayers; int baseMatrixSize = compact ? 11 + layers*4 : 14 + layers*4; // not including alignment lines int[] alignmentMap = new int[baseMatrixSize]; bool[] rawbits = new bool[totalBitsInLayer(layers, compact)]; if (compact) { for (int i = 0; i < alignmentMap.Length; i++) { alignmentMap[i] = i; } } else { int matrixSize = baseMatrixSize + 1 + 2*((baseMatrixSize/2 - 1)/15); int origCenter = baseMatrixSize/2; int center = matrixSize/2; for (int i = 0; i < origCenter; i++) { int newOffset = i + i/15; alignmentMap[origCenter - i - 1] = center - newOffset - 1; alignmentMap[origCenter + i] = center + newOffset + 1; } } for (int i = 0, rowOffset = 0; i < layers; i++) { int rowSize = compact ? (layers - i)*4 + 9 : (layers - i)*4 + 12; // The top-left most point of this layer is (not including alignment lines) int low = i*2; // The bottom-right most point of this layer is (not including alignment lines) int high = baseMatrixSize - 1 - low; // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows for (int j = 0; j < rowSize; j++) { int columnOffset = j*2; for (int k = 0; k < 2; k++) { // left column rawbits[rowOffset + columnOffset + k] = matrix[alignmentMap[low + k], alignmentMap[low + j]]; // bottom row rawbits[rowOffset + 2*rowSize + columnOffset + k] = matrix[alignmentMap[low + j], alignmentMap[high - k]]; // right column rawbits[rowOffset + 4*rowSize + columnOffset + k] = matrix[alignmentMap[high - k], alignmentMap[high - j]]; // top row rawbits[rowOffset + 6*rowSize + columnOffset + k] = matrix[alignmentMap[high - j], alignmentMap[low + k]]; } } rowOffset += rowSize*8; } return rawbits; } /// /// Reads a code of given length and at given index in an array of bits /// /// The rawbits. /// The start index. /// The length. /// private static int readCode(bool[] rawbits, int startIndex, int length) { int res = 0; for (int i = startIndex; i < startIndex + length; i++) { res <<= 1; if (rawbits[i]) { res++; } } return res; } private static int totalBitsInLayer(int layers, bool compact) { return ((compact ? 88 : 112) + 16*layers)*layers; } } }