diff --git a/csharp/MultiFormatReader.cs b/csharp/MultiFormatReader.cs index 9c0f09ca1..74a04ed04 100755 --- a/csharp/MultiFormatReader.cs +++ b/csharp/MultiFormatReader.cs @@ -16,6 +16,7 @@ using System; using System.Collections; using com.google.zxing.qrcode; using com.google.zxing.oned; +using com.google.zxing.datamatrix; namespace com.google.zxing { @@ -116,7 +117,7 @@ namespace com.google.zxing } // TODO re-enable once Data Matrix is ready if (possibleFormats.Contains(BarcodeFormat.DATAMATRIX)) { - //readers.Add(new DataMatrixReader()); + readers.Add(new DataMatrixReader()); } // At end in "try harder" mode if (addOneDReader && tryHarder) @@ -129,14 +130,14 @@ namespace com.google.zxing { if (!tryHarder) { - //readers.Add(new MultiFormatOneDReader(hints)); + readers.Add(new MultiFormatOneDReader(hints)); } readers.Add(new QRCodeReader()); // TODO re-enable once Data Matrix is ready - // readers.addElement(new DataMatrixReader()); + readers.Add(new DataMatrixReader()); if (tryHarder) { - //readers.Add(new MultiFormatOneDReader(hints)); + readers.Add(new MultiFormatOneDReader(hints)); } } } diff --git a/csharp/bin/Release/com.google.zxing.dll b/csharp/bin/Release/com.google.zxing.dll new file mode 100644 index 000000000..107727e17 Binary files /dev/null and b/csharp/bin/Release/com.google.zxing.dll differ diff --git a/csharp/bin/Release/com.google.zxing.pdb b/csharp/bin/Release/com.google.zxing.pdb new file mode 100644 index 000000000..3aef4741f Binary files /dev/null and b/csharp/bin/Release/com.google.zxing.pdb differ diff --git a/csharp/datamatrix/DataMatrixReader.cs b/csharp/datamatrix/DataMatrixReader.cs new file mode 100644 index 000000000..4c8761225 --- /dev/null +++ b/csharp/datamatrix/DataMatrixReader.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using com.google.zxing; +using com.google.zxing.common; +using com.google.zxing.datamatrix.decoder; +using com.google.zxing.datamatrix.detector; + +namespace com.google.zxing.datamatrix +{ + public sealed class DataMatrixReader + { + private static ResultPoint[] NO_POINTS = new ResultPoint[0]; + private Decoder decoder = new Decoder(); + + /** + * Locates and decodes a Data Matrix code in an image. + * + * @return a String representing the content encoded by the Data Matrix code + * @throws ReaderException if a Data Matrix code cannot be found, or cannot be decoded + */ + public Result decode(MonochromeBitmapSource image) { + return decode(image, null); + } + + public Result decode(MonochromeBitmapSource image, System.Collections.Hashtable hints) + { + DecoderResult decoderResult; + ResultPoint[] points; + if (hints != null && hints.ContainsKey(DecodeHintType.PURE_BARCODE)) { + BitMatrix bits = extractPureBits(image); + decoderResult = decoder.decode(bits); + points = NO_POINTS; + } else { + DetectorResult detectorResult = new Detector(image).detect(); + decoderResult = decoder.decode(detectorResult.getBits()); + points = detectorResult.getPoints(); + } + Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.DATAMATRIX); + if (decoderResult.getByteSegments() != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments()); + } + return result; + } + + /** + * This method detects a Data Matrix code in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a Data Matrix code, with some white border + * around it. This is a specialized method that works exceptionally fast in this special + * case. + */ + private static BitMatrix extractPureBits(MonochromeBitmapSource image) { + // Now need to determine module size in pixels + + int height = image.getHeight(); + int width = image.getWidth(); + int minDimension = Math.Min(height, width); + + // First, skip white border by tracking diagonally from the top left down and to the right: + int borderWidth = 0; + while (borderWidth < minDimension && !image.isBlack(borderWidth, borderWidth)) { + borderWidth++; + } + if (borderWidth == minDimension) { + throw new ReaderException(); + } + + // And then keep tracking across the top-left black module to determine module size + int moduleEnd = borderWidth + 1; + while (moduleEnd < width && image.isBlack(moduleEnd, borderWidth)) { + moduleEnd++; + } + if (moduleEnd == width) { + throw new ReaderException(); + } + + int moduleSize = moduleEnd - borderWidth; + + // And now find where the bottommost black module on the first column ends + int columnEndOfSymbol = height - 1; + while (columnEndOfSymbol >= 0 && !image.isBlack(borderWidth, columnEndOfSymbol)) { + columnEndOfSymbol--; + } + if (columnEndOfSymbol < 0) { + throw new ReaderException(); + } + columnEndOfSymbol++; + + // Make sure width of barcode is a multiple of module size + if ((columnEndOfSymbol - borderWidth) % moduleSize != 0) { + throw new ReaderException(); + } + int dimension = (columnEndOfSymbol - borderWidth) / moduleSize; + + // Push in the "border" by half the module width so that we start + // sampling in the middle of the module. Just in case the image is a + // little off, this will help recover. + borderWidth += moduleSize >> 1; + + int sampleDimension = borderWidth + (dimension - 1) * moduleSize; + if (sampleDimension >= width || sampleDimension >= height) { + throw new ReaderException(); + } + + // Now just read off the bits + BitMatrix bits = new BitMatrix(dimension); + for (int i = 0; i < dimension; i++) { + int iOffset = borderWidth + i * moduleSize; + for (int j = 0; j < dimension; j++) { + if (image.isBlack(borderWidth + j * moduleSize, iOffset)) { + bits.set(i, j); + } + } + } + return bits; + } + } +} diff --git a/csharp/datamatrix/decoder/BitMatrixParser.cs b/csharp/datamatrix/decoder/BitMatrixParser.cs new file mode 100644 index 000000000..653810a4c --- /dev/null +++ b/csharp/datamatrix/decoder/BitMatrixParser.cs @@ -0,0 +1,450 @@ +/* + * Copyright 2007 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.Linq; +using System.Text; +using com.google.zxing.common; + +namespace com.google.zxing.datamatrix.decoder +{ + /** + * @author bbrown@google.com (Brian Brown) + */ + public sealed class BitMatrixParser + { + + private BitMatrix mappingBitMatrix; + private BitMatrix readMappingMatrix; + private Version version; + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws ReaderException if dimension is < 10 or > 144 or not 0 mod 2 + */ + public BitMatrixParser(BitMatrix bitMatrix) { + int dimension = bitMatrix.getDimension(); + if (dimension < 10 || dimension > 144 || (dimension & 0x01) != 0) { + throw new ReaderException(); + } + + version = readVersion(bitMatrix); + this.mappingBitMatrix = extractDataRegion(bitMatrix); + // TODO(bbrown): Make this work for rectangular symbols + this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getDimension()); + } + + /** + *

Creates the version object based on the dimension of the original bit matrix from + * the datamatrix code.

+ * + *

See ISO 16022:2006 Table 7 - ECC 200 symbol attributes

+ * + * @param bitMatrix Original {@link BitMatrix} including alignment patterns + * @return {@link Version} encapsulating the Data Matrix Code's "version" + * @throws ReaderException if the dimensions of the mapping matrix are not valid + * Data Matrix dimensions. + */ + public Version readVersion(BitMatrix bitMatrix) { + + if (version != null) { + return version; + } + + // TODO(bbrown): make this work for rectangular dimensions as well. + int numRows = bitMatrix.getDimension(); + int numColumns = numRows; + + return Version.getVersionForDimensions(numRows, numColumns); + } + + /** + *

Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns) + * in the correct order in order to reconstitute the codewords bytes contained within the + * Data Matrix Code.

+ * + * @return bytes encoded within the Data Matrix Code + * @throws ReaderException if the exact number of bytes expected is not read + */ + public sbyte[] readCodewords() { + + sbyte[] result = new sbyte[version.getTotalCodewords()]; + int resultOffset = 0; + + int row = 4; + int column = 0; + // TODO(bbrown): Data Matrix can be rectangular, assuming square for now + int numRows = mappingBitMatrix.getDimension(); + int numColumns = numRows; + + bool corner1Read = false; + bool corner2Read = false; + bool corner3Read = false; + bool corner4Read = false; + + // Read all of the codewords + do { + // Check the four corner cases + if ((row == numRows) && (column == 0) && !corner1Read) { + result[resultOffset++] = (sbyte) readCorner1(numRows, numColumns); + row -= 2; + column +=2; + corner1Read = true; + } else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x03) != 0) && !corner2Read) { + result[resultOffset++] = (sbyte)readCorner2(numRows, numColumns); + row -= 2; + column +=2; + corner2Read = true; + } else if ((row == numRows+4) && (column == 2) && ((numColumns & 0x07) == 0) && !corner3Read) { + result[resultOffset++] = (sbyte)readCorner3(numRows, numColumns); + row -= 2; + column +=2; + corner3Read = true; + } else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x07) == 4) && !corner4Read) { + result[resultOffset++] = (sbyte)readCorner4(numRows, numColumns); + row -= 2; + column +=2; + corner4Read = true; + } else { + // Sweep upward diagonally to the right + do { + if ((row < numRows) && (column >= 0) && !readMappingMatrix.get(row, column)) { + result[resultOffset++] = (sbyte)readUtah(row, column, numRows, numColumns); + } + row -= 2; + column +=2; + } while ((row >= 0) && (column < numColumns)); + row += 1; + column +=3; + + // Sweep downward diagonally to the left + do { + if ((row >= 0) && (column < numColumns) && !readMappingMatrix.get(row, column)) { + result[resultOffset++] = (sbyte) readUtah(row, column, numRows, numColumns); + } + row += 2; + column -=2; + } while ((row < numRows) && (column >= 0)); + row += 3; + column +=1; + } + } while ((row < numRows) || (column < numColumns)); + + if (resultOffset != version.getTotalCodewords()) { + throw new ReaderException(); + } + return result; + } + + /** + *

Reads a bit of the mapping matrix accounting for boundary wrapping.

+ * + * @param row Row to read in the mapping matrix + * @param column Column to read in the mapping matrix + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return value of the given bit in the mapping matrix + */ + bool readModule(int row, int column, int numRows, int numColumns) { + // Adjust the row and column indices based on boundary wrapping + if (row < 0) { + row += numRows; + column += 4 - ((numRows + 4) & 0x07); + } + if (column < 0) { + column += numColumns; + row += 4 - ((numColumns + 4) & 0x07); + } + readMappingMatrix.set(row, column); + return mappingBitMatrix.get(row, column); + } + + /** + *

Reads the 8 bits of the standard utah shaped pattern.

+ * + *

See ISO 16022:2006, 5.8.1 Figure 6

+ * + * @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern + * @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the utah shape + */ + int readUtah(int row, int column, int numRows, int numColumns) { + int currentByte = 0; + if (readModule(row - 2, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 2, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 1, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 1, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row - 1, column, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row, column - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row, column - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(row, column, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 1.

+ * + *

See ISO 16022:2006, Figure F.3

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 1 + */ + int readCorner1(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(2, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(3, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 2.

+ * + *

See ISO 16022:2006, Figure F.4

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 2 + */ + int readCorner2(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 3, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 2, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 4, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 3.

+ * + *

See ISO 16022:2006, Figure F.5

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 3 + */ + int readCorner3(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 3, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Reads the 8 bits of the special corner condition 4.

+ * + *

See ISO 16022:2006, Figure F.6

+ * + * @param numRows Number of rows in the mapping matrix + * @param numColumns Number of columns in the mapping matrix + * @return byte from the Corner condition 4 + */ + int readCorner4(int numRows, int numColumns) { + int currentByte = 0; + if (readModule(numRows - 3, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 2, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(numRows - 1, 0, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 2, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(0, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(1, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(2, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + currentByte <<= 1; + if (readModule(3, numColumns - 1, numRows, numColumns)) { + currentByte |= 1; + } + return currentByte; + } + + /** + *

Extracts the data region from a {@link BitMatrix} that contains + * alignment patterns.

+ * + * @param bitMatrix Original {@link BitMatrix} with alignment patterns + * @return BitMatrix that has the alignment patterns removed + */ + BitMatrix extractDataRegion(BitMatrix bitMatrix) { + int symbolSizeRows = version.getSymbolSizeRows(); + int symbolSizeColumns = version.getSymbolSizeColumns(); + + // TODO(bbrown): Make this work with rectangular codes + if (bitMatrix.getDimension() != symbolSizeRows) { + throw new ArgumentException("Dimension of bitMarix must match the version size"); + } + + int dataRegionSizeRows = version.getDataRegionSizeRows(); + int dataRegionSizeColumns = version.getDataRegionSizeColumns(); + + int numDataRegionsRow = symbolSizeRows / dataRegionSizeRows; + int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns; + + int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows; + //int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns; + + // TODO(bbrown): Make this work with rectangular codes + BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionRow); + for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) { + int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows; + for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) { + int dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns; + for (int i = 0; i < dataRegionSizeRows; ++i) { + int readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i; + int writeRowOffset = dataRegionRowOffset + i; + for (int j = 0; j < dataRegionSizeColumns; ++j) { + int readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j; + if (bitMatrix.get(readRowOffset, readColumnOffset)) { + int writeColumnOffset = dataRegionColumnOffset + j; + bitMatrixWithoutAlignment.set(writeRowOffset, writeColumnOffset); + } + } + } + } + } + return bitMatrixWithoutAlignment; + } + + } +} diff --git a/csharp/datamatrix/decoder/DataBlock.cs b/csharp/datamatrix/decoder/DataBlock.cs new file mode 100644 index 000000000..8e4893e04 --- /dev/null +++ b/csharp/datamatrix/decoder/DataBlock.cs @@ -0,0 +1,124 @@ +/* + * Copyright 2008 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.Linq; +using System.Text; + +namespace com.google.zxing.datamatrix.decoder +{ + + /** + *

Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.

+ * + * @author bbrown@google.com (Brian Brown) + */ + public sealed class DataBlock + { + private int numDataCodewords; + private sbyte[] codewords; + + private DataBlock(int numDataCodewords, sbyte[] codewords) { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + } + + /** + *

When Data Matrix Codes use multiple data blocks, they actually interleave the bytes of each of them. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks.

+ * + * @param rawCodewords bytes as read directly from the Data Matrix Code + * @param version version of the Data Matrix Code + * @return {@link DataBlock}s containing original bytes, "de-interleaved" from representation in the + * Data Matrix Code + */ + public static DataBlock[] getDataBlocks(sbyte[] rawCodewords, + Version version) { + // Figure out the number and size of data blocks used by this version + Version.ECBlocks ecBlocks = version.getECBlocks(); + + // First count the total number of data blocks + int totalBlocks = 0; + Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); + for (int i = 0; i < ecBlockArray.Length; i++) { + totalBlocks += ecBlockArray[i].getCount(); + } + + // Now establish DataBlocks of the appropriate size and number of data codewords + DataBlock[] result = new DataBlock[totalBlocks]; + int numResultBlocks = 0; + for (int j = 0; j < ecBlockArray.Length; j++) { + Version.ECB ecBlock = ecBlockArray[j]; + for (int i = 0; i < ecBlock.getCount(); i++) { + int numDataCodewords = ecBlock.getDataCodewords(); + int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; + result[numResultBlocks++] = new DataBlock(numDataCodewords, new sbyte[numBlockCodewords]); + } + } + + // All blocks have the same amount of data, except that the last n + // (where n may be 0) have 1 less byte. Figure out where these start. + // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144 + int longerBlocksTotalCodewords = result[0].codewords.Length; + //int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1; + + int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords(); + int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1; + // The last elements of result may be 1 element shorter for 144 matrix + // first fill out as many elements as all of them have minus 1 + int rawCodewordsOffset = 0; + for (int i = 0; i < shorterBlocksNumDataCodewords; i++) { + for (int j = 0; j < numResultBlocks; j++) { + result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; + } + } + + // Fill out the last data block in the longer ones + bool specialVersion = version.getVersionNumber() == 24; + int numLongerBlocks = specialVersion ? 8 : numResultBlocks; + for (int j = 0; j < numLongerBlocks; j++) { + result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; + } + + // Now add in error correction blocks + int max = result[0].codewords.Length; + for (int i = longerBlocksNumDataCodewords; i < max; i++) { + for (int j = 0; j < numResultBlocks; j++) { + int iOffset = (specialVersion && j > 7) ? i - 1 : i; + result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + + if (rawCodewordsOffset != rawCodewords.Length) { + throw new ArgumentException(); + } + + return result; + } + + public int getNumDataCodewords() { + return numDataCodewords; + } + + public sbyte[] getCodewords() { + return codewords; + } + } +} diff --git a/csharp/datamatrix/decoder/DecodedBitStreamParser.cs b/csharp/datamatrix/decoder/DecodedBitStreamParser.cs new file mode 100644 index 000000000..15bf3220a --- /dev/null +++ b/csharp/datamatrix/decoder/DecodedBitStreamParser.cs @@ -0,0 +1,449 @@ +/* + * Copyright 2008 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.Linq; +using System.Text; +using com.google.zxing.common; + +namespace com.google.zxing.datamatrix.decoder +{ + /** + *

Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.

+ * + * @author bbrown@google.com (Brian Brown) + */ + public sealed class DecodedBitStreamParser + { + /** + * See ISO 16022:2006, Annex C Table C.1 + * The C40 Basic Character Set (*'s used for placeholders for the shift values) + */ + private static char[] C40_BASIC_SET_CHARS = { + '*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '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' + }; + + private static char[] C40_SHIFT2_SET_CHARS = { + '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', + '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_' + }; + + /** + * See ISO 16022:2006, Annex C Table C.2 + * The Text Basic Character Set (*'s used for placeholders for the shift values) + */ + private static char[] TEXT_BASIC_SET_CHARS = { + '*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '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' + }; + + private static char[] TEXT_SHIFT3_SET_CHARS = { + '\'', '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', '{', '|', '}', '~', (char) 127 + }; + + private const int PAD_ENCODE = 0; // Not really an encoding + private const int ASCII_ENCODE = 1; + private const int C40_ENCODE = 2; + private const int TEXT_ENCODE = 3; + private const int ANSIX12_ENCODE = 4; + private const int EDIFACT_ENCODE = 5; + private const int BASE256_ENCODE = 6; + + private DecodedBitStreamParser() { + } + + public static DecoderResult decode(sbyte[] bytes) { + BitSource bits = new BitSource(bytes); + StringBuilder result = new StringBuilder(); + StringBuilder resultTrailer = new StringBuilder(0); + System.Collections.ArrayList byteSegments = new System.Collections.ArrayList(1); + int mode = ASCII_ENCODE; + do { + if (mode == ASCII_ENCODE) { + mode = decodeAsciiSegment(bits, result, resultTrailer); + } else { + switch (mode) { + case C40_ENCODE: + decodeC40Segment(bits, result); + break; + case TEXT_ENCODE: + decodeTextSegment(bits, result); + break; + case ANSIX12_ENCODE: + decodeAnsiX12Segment(bits, result); + break; + case EDIFACT_ENCODE: + decodeEdifactSegment(bits, result); + break; + case BASE256_ENCODE: + decodeBase256Segment(bits, result, byteSegments); + break; + default: + throw new ReaderException(); + } + mode = ASCII_ENCODE; + } + } while (mode != PAD_ENCODE && bits.available() > 0); + if (resultTrailer.Length > 0) { + result.Append(resultTrailer); + } + return new DecoderResult(bytes, result.ToString(), int.Equals(byteSegments.Count,0) ? null : byteSegments); + } + + /** + * See ISO 16022:2006, 5.2.3 and Annex C, Table C.2 + */ + private static int decodeAsciiSegment(BitSource bits, StringBuilder result, StringBuilder resultTrailer) + { + bool upperShift = false; + do { + int oneByte = bits.readBits(8); + if (oneByte == 0) { + throw new ReaderException(); + } else if (oneByte <= 128) { // ASCII data (ASCII value + 1) + oneByte = upperShift ? (oneByte + 128) : oneByte; + upperShift = false; + result.Append((char) (oneByte - 1)); + return ASCII_ENCODE; + } else if (oneByte == 129) { // Pad + return PAD_ENCODE; + } else if (oneByte <= 229) { // 2-digit data 00-99 (Numeric Value + 130) + int value = oneByte - 130; + if (value < 10) { // padd with '0' for single digit values + result.Append('0'); + } + result.Append(value); + } else if (oneByte == 230) { // Latch to C40 encodation + return C40_ENCODE; + } else if (oneByte == 231) { // Latch to Base 256 encodation + return BASE256_ENCODE; + } else if (oneByte == 232) { // FNC1 + throw new ReaderException(); + } else if (oneByte == 233) { // Structured Append + throw new ReaderException(); + } else if (oneByte == 234) { // Reader Programming + throw new ReaderException(); + } else if (oneByte == 235) { // Upper Shift (shift to Extended ASCII) + upperShift = true; + } else if (oneByte == 236) { // 05 Macro + result.Append("[)>\u001E05\u001D"); + resultTrailer.Insert(0, "\u001E\u0004"); + } else if (oneByte == 237) { // 06 Macro + result.Append("[)>\u001E06\u001D"); + resultTrailer.Insert(0, "\u001E\u0004"); + } else if (oneByte == 238) { // Latch to ANSI X12 encodation + return ANSIX12_ENCODE; + } else if (oneByte == 239) { // Latch to Text encodation + return TEXT_ENCODE; + } else if (oneByte == 240) { // Latch to EDIFACT encodation + return EDIFACT_ENCODE; + } else if (oneByte == 241) { // ECI Character + // TODO(bbrown): I think we need to support ECI + throw new ReaderException(); + } else if (oneByte >= 242) { // Not to be used in ASCII encodation + throw new ReaderException(); + } + } while (bits.available() > 0); + return ASCII_ENCODE; + } + + /** + * See ISO 16022:2006, 5.2.5 and Annex C, Table C.1 + */ + private static void decodeC40Segment(BitSource bits, StringBuilder result) { + // Three C40 values are encoded in a 16-bit value as + // (1600 * C1) + (40 * C2) + C3 + 1 + // TODO(bbrown): The Upper Shift with C40 doesn't work in the 4 value scenario all the time + bool upperShift = false; + + int[] cValues = new int[3]; + do { + // If there is only one byte left then it will be encoded as ASCII + if (bits.available() == 8) { + return; + } + int firstByte = bits.readBits(8); + if (firstByte == 254) { // Unlatch codeword + return; + } + + parseTwoBytes(firstByte, bits.readBits(8), cValues); + + int shift = 0; + for (int i = 0; i < 3; i++) { + int cValue = cValues[i]; + switch (shift) { + case 0: + if (cValue < 3) { + shift = cValue + 1; + } else { + if (upperShift) { + result.Append((char) (C40_BASIC_SET_CHARS[cValue] + 128)); + upperShift = false; + } else { + result.Append(C40_BASIC_SET_CHARS[cValue]); + } + } + break; + case 1: + if (upperShift) { + result.Append((char) (cValue + 128)); + upperShift = false; + } else { + result.Append(cValue); + } + shift = 0; + break; + case 2: + if (cValue < 27) { + if (upperShift) { + result.Append((char) (C40_SHIFT2_SET_CHARS[cValue] + 128)); + upperShift = false; + } else { + result.Append(C40_SHIFT2_SET_CHARS[cValue]); + } + } else if (cValue == 27) { // FNC1 + throw new ReaderException(); + } else if (cValue == 30) { // Upper Shift + upperShift = true; + } else { + throw new ReaderException(); + } + shift = 0; + break; + case 3: + if (upperShift) { + result.Append((char) (cValue + 224)); + upperShift = false; + } else { + result.Append((char) (cValue + 96)); + } + shift = 0; + break; + default: + throw new ReaderException(); + } + } + } while (bits.available() > 0); + } + + /** + * See ISO 16022:2006, 5.2.6 and Annex C, Table C.2 + */ + private static void decodeTextSegment(BitSource bits, StringBuilder result) { + // Three Text values are encoded in a 16-bit value as + // (1600 * C1) + (40 * C2) + C3 + 1 + // TODO(bbrown): The Upper Shift with Text doesn't work in the 4 value scenario all the time + bool upperShift = false; + + int[] cValues = new int[3]; + do { + // If there is only one byte left then it will be encoded as ASCII + if (bits.available() == 8) { + return; + } + int firstByte = bits.readBits(8); + if (firstByte == 254) { // Unlatch codeword + return; + } + + parseTwoBytes(firstByte, bits.readBits(8), cValues); + + int shift = 0; + for (int i = 0; i < 3; i++) { + int cValue = cValues[i]; + switch (shift) { + case 0: + if (cValue < 3) { + shift = cValue + 1; + } else { + if (upperShift) { + result.Append((char) (TEXT_BASIC_SET_CHARS[cValue] + 128)); + upperShift = false; + } else { + result.Append(TEXT_BASIC_SET_CHARS[cValue]); + } + } + break; + case 1: + if (upperShift) { + result.Append((char) (cValue + 128)); + upperShift = false; + } else { + result.Append(cValue); + } + shift = 0; + break; + case 2: + // Shift 2 for Text is the same encoding as C40 + if (cValue < 27) { + if (upperShift) { + result.Append((char) (C40_SHIFT2_SET_CHARS[cValue] + 128)); + upperShift = false; + } else { + result.Append(C40_SHIFT2_SET_CHARS[cValue]); + } + } else if (cValue == 27) { // FNC1 + throw new ReaderException(); + } else if (cValue == 30) { // Upper Shift + upperShift = true; + } else { + throw new ReaderException(); + } + shift = 0; + break; + case 3: + if (upperShift) { + result.Append((char) (TEXT_SHIFT3_SET_CHARS[cValue] + 128)); + upperShift = false; + } else { + result.Append(TEXT_SHIFT3_SET_CHARS[cValue]); + } + shift = 0; + break; + default: + throw new ReaderException(); + } + } + } while (bits.available() > 0); + } + + /** + * See ISO 16022:2006, 5.2.7 + */ + private static void decodeAnsiX12Segment(BitSource bits, StringBuilder result) { + // Three ANSI X12 values are encoded in a 16-bit value as + // (1600 * C1) + (40 * C2) + C3 + 1 + + int[] cValues = new int[3]; + do { + // If there is only one byte left then it will be encoded as ASCII + if (bits.available() == 8) { + return; + } + int firstByte = bits.readBits(8); + if (firstByte == 254) { // Unlatch codeword + return; + } + + parseTwoBytes(firstByte, bits.readBits(8), cValues); + + for (int i = 0; i < 3; i++) { + int cValue = cValues[i]; + if (cValue == 0) { // X12 segment terminator + result.Append('\r'); + } else if (cValue == 1) { // X12 segment separator * + result.Append('*'); + } else if (cValue == 2) { // X12 sub-element separator > + result.Append('>'); + } else if (cValue == 3) { // space + result.Append(' '); + } else if (cValue < 14) { // 0 - 9 + result.Append((char) (cValue + 44)); + } else if (cValue < 40) { // A - Z + result.Append((char) (cValue + 51)); + } else { + throw new ReaderException(); + } + } + } while (bits.available() > 0); + } + + private static void parseTwoBytes(int firstByte, int secondByte, int[] result) { + int fullBitValue = (firstByte << 8) + secondByte - 1; + int temp = fullBitValue / 1600; + result[0] = temp; + fullBitValue -= temp * 1600; + temp = fullBitValue / 40; + result[1] = temp; + result[2] = fullBitValue - temp * 40; + } + + /** + * See ISO 16022:2006, 5.2.8 and Annex C Table C.3 + */ + private static void decodeEdifactSegment(BitSource bits, StringBuilder result) { + bool unlatch = false; + do { + // If there is only two or less bytes left then it will be encoded as ASCII + if (bits.available() <= 16) { + return; + } + + for (int i = 0; i < 4; i++) { + int edifactValue = bits.readBits(6); + + // Check for the unlatch character + if (edifactValue == 0x2B67) { // 011111 + unlatch = true; + // If we encounter the unlatch code then continue reading because the Codeword triple + // is padded with 0's + } + + if (!unlatch) { + if ((edifactValue & 32) == 0) { // no 1 in the leading (6th) bit + edifactValue |= 64; // Add a leading 01 to the 6 bit binary value + } + result.Append(edifactValue); + } + } + } while (!unlatch && bits.available() > 0); + } + + /** + * See ISO 16022:2006, 5.2.9 and Annex B, B.2 + */ + private static void decodeBase256Segment(BitSource bits, StringBuilder result, System.Collections.ArrayList byteSegments) { + // Figure out how long the Base 256 Segment is. + int d1 = bits.readBits(8); + int count; + if (d1 == 0) { // Read the remainder of the symbol + count = bits.available() / 8; + } else if (d1 < 250) { + count = d1; + } else { + count = 250 * (d1 - 249) + bits.readBits(8); + } + byte[] bytes = new byte[count]; + for (int i = 0; i < count; i++) { + bytes[i] = unrandomize255State(bits.readBits(8), i); + } + byteSegments.Add(bytes); + try { + result.Append(System.Text.Encoding.GetEncoding("iso-8859-1").GetString(bytes)); + } catch (Exception uee) { + throw new Exception("Platform does not support required encoding: " + uee); + } + } + + /** + * See ISO 16022:2006, Annex B, B.2 + */ + private static byte unrandomize255State(int randomizedBase256Codeword, + int base256CodewordPosition) { + int pseudoRandomNumber = ((149 * base256CodewordPosition) % 255) + 1; + int tempVariable = randomizedBase256Codeword - pseudoRandomNumber; + return (byte) (tempVariable >= 0 ? tempVariable : (tempVariable + 256)); + } + } +} diff --git a/csharp/datamatrix/decoder/Decoder.cs b/csharp/datamatrix/decoder/Decoder.cs new file mode 100644 index 000000000..d30b4be9f --- /dev/null +++ b/csharp/datamatrix/decoder/Decoder.cs @@ -0,0 +1,132 @@ +/* + * Copyright 2007 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.Linq; +using System.Text; +using com.google.zxing.common; +using com.google.zxing.common.reedsolomon; + +namespace com.google.zxing.datamatrix.decoder +{ + + /** + *

The main class which implements Data Matrix Code decoding -- as opposed to locating and extracting + * the Data Matrix Code from an image.

+ * + * @author bbrown@google.com (Brian Brown) + */ + public sealed class Decoder + { + private ReedSolomonDecoder rsDecoder; + public Decoder() { + rsDecoder = new ReedSolomonDecoder(GF256.DATA_MATRIX_FIELD); + } + + /** + *

Convenience method that can decode a Data Matrix Code represented as a 2D array of booleans. + * "true" is taken to mean a black module.

+ * + * @param image booleans representing white/black Data Matrix Code modules + * @return text and bytes encoded within the Data Matrix Code + * @throws ReaderException if the Data Matrix Code cannot be decoded + */ + public DecoderResult decode(bool[][] image) { + int dimension = image.Length; + BitMatrix bits = new BitMatrix(dimension); + for (int i = 0; i < dimension; i++) { + for (int j = 0; j < dimension; j++) { + if (image[i][j]) { + bits.set(i, j); + } + } + } + return decode(bits); + } + + /** + *

Decodes a Data Matrix Code represented as a {@link BitMatrix}. A 1 or "true" is taken + * to mean a black module.

+ * + * @param bits booleans representing white/black Data Matrix Code modules + * @return text and bytes encoded within the Data Matrix Code + * @throws ReaderException if the Data Matrix Code cannot be decoded + */ + public DecoderResult decode(BitMatrix bits) { + + // Construct a parser and read version, error-correction level + BitMatrixParser parser = new BitMatrixParser(bits); + Version version = parser.readVersion(bits); + + // Read codewords + sbyte[] codewords = parser.readCodewords(); + // Separate into data blocks + DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version); + + // Count total number of data bytes + int totalBytes = 0; + for (int i = 0; i < dataBlocks.Length; i++) { + totalBytes += dataBlocks[i].getNumDataCodewords(); + } + sbyte[] resultBytes = new sbyte[totalBytes]; + int resultOffset = 0; + + // Error-correct and copy data blocks together into a stream of bytes + for (int j = 0; j < dataBlocks.Length; j++) { + DataBlock dataBlock = dataBlocks[j]; + sbyte[] codewordBytes = dataBlock.getCodewords(); + int numDataCodewords = dataBlock.getNumDataCodewords(); + correctErrors(codewordBytes, numDataCodewords); + for (int i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset++] = codewordBytes[i]; + } + } + + // Decode the contents of that stream of bytes + return DecodedBitStreamParser.decode(resultBytes); + } + + /** + *

Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place using Reed-Solomon error correction.

+ * + * @param codewordBytes data and error correction codewords + * @param numDataCodewords number of codewords that are data bytes + * @throws ReaderException if error correction fails + */ + private void correctErrors(sbyte[] codewordBytes, int numDataCodewords) { + int numCodewords = codewordBytes.Length; + // First read into an array of ints + int[] codewordsInts = new int[numCodewords]; + for (int i = 0; i < numCodewords; i++) { + codewordsInts[i] = codewordBytes[i] & 0xFF; + } + int numECCodewords = codewordBytes.Length - numDataCodewords; + try { + rsDecoder.decode(codewordsInts, numECCodewords); + } catch (ReedSolomonException rse) { + throw new ReaderException(); + } + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords + for (int i = 0; i < numDataCodewords; i++) { + codewordBytes[i] = (sbyte) codewordsInts[i]; + } + } + + } +} diff --git a/csharp/datamatrix/decoder/Version.cs b/csharp/datamatrix/decoder/Version.cs new file mode 100644 index 000000000..25e32741f --- /dev/null +++ b/csharp/datamatrix/decoder/Version.cs @@ -0,0 +1,222 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace com.google.zxing.datamatrix.decoder +{ + public sealed class Version + { + private static Version[] VERSIONS = buildVersions(); + private int versionNumber; + private int symbolSizeRows; + private int symbolSizeColumns; + private int dataRegionSizeRows; + private int dataRegionSizeColumns; + private ECBlocks ecBlocks; + private int totalCodewords; + + private Version(int versionNumber, + int symbolSizeRows, + int symbolSizeColumns, + int dataRegionSizeRows, + int dataRegionSizeColumns, + ECBlocks ecBlocks) { + this.versionNumber = versionNumber; + this.symbolSizeRows = symbolSizeRows; + this.symbolSizeColumns = symbolSizeColumns; + this.dataRegionSizeRows = dataRegionSizeRows; + this.dataRegionSizeColumns = dataRegionSizeColumns; + this.ecBlocks = ecBlocks; + + // Calculate the total number of codewords + int total = 0; + int ecCodewords = ecBlocks.getECCodewords(); + ECB[] ecbArray = ecBlocks.getECBlocks(); + for (int i = 0; i < ecbArray.Length; i++) { + ECB ecBlock = ecbArray[i]; + total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords); + } + this.totalCodewords = total; + } + + public int getVersionNumber() { + return versionNumber; + } + + public int getSymbolSizeRows() { + return symbolSizeRows; + } + + public int getSymbolSizeColumns() { + return symbolSizeColumns; + } + + public int getDataRegionSizeRows() { + return dataRegionSizeRows; + } + + public int getDataRegionSizeColumns() { + return dataRegionSizeColumns; + } + + public int getTotalCodewords() { + return totalCodewords; + } + + public ECBlocks getECBlocks() { + return ecBlocks; + } + + /** + *

Deduces version information from Data Matrix dimensions.

+ * + * @param numRows Number of rows in modules + * @param numColumns Number of columns in modules + * @return {@link Version} for a Data Matrix Code of those dimensions + * @throws ReaderException if dimensions do correspond to a valid Data Matrix size + */ + public static Version getVersionForDimensions(int numRows, int numColumns) { + if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) { + throw new ReaderException(); + } + + // TODO(bbrown): This is doing a linear search through the array of versions. + // If we interleave the rectangular versions with the square versions we could + // do a binary search. + int numVersions = VERSIONS.Length; + for (int i = 0; i < numVersions; ++i){ + Version version = VERSIONS[i]; + if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) { + return version; + } + } + + throw new ReaderException(); + } + + /** + *

Encapsulates a set of error-correction blocks in one symbol version. Most versions will + * use blocks of differing sizes within one version, so, this encapsulates the parameters for + * each set of blocks. It also holds the number of error-correction codewords per block since it + * will be the same across all blocks within one version.

+ */ + public class ECBlocks { + private int ecCodewords; + private ECB[] ecBlocks; + + public ECBlocks(int ecCodewords, ECB ecBlocks) { + this.ecCodewords = ecCodewords; + this.ecBlocks = new ECB[] { ecBlocks }; + } + + public ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) { + this.ecCodewords = ecCodewords; + this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 }; + } + + public int getECCodewords() { + return ecCodewords; + } + + public ECB[] getECBlocks() { + return ecBlocks; + } + } + + /** + *

Encapsualtes the parameters for one error-correction block in one symbol version. + * This includes the number of data codewords, and the number of times a block with these + * parameters is used consecutively in the Data Matrix code version's format.

+ */ + public class ECB { + private int count; + private int dataCodewords; + + public ECB(int count, int dataCodewords) { + this.count = count; + this.dataCodewords = dataCodewords; + } + + public int getCount() { + return count; + } + + public int getDataCodewords() { + return dataCodewords; + } + } + + public String toString() { + return versionNumber.ToString(); + } + + /** + * See ISO 16022:2006 5.5.1 Table 7 + */ + private static Version[] buildVersions() { + return new Version[]{ + new Version(1, 10, 10, 8, 8, + new ECBlocks(5, new ECB(1, 3))), + new Version(2, 12, 12, 10, 10, + new ECBlocks(7, new ECB(1, 5))), + new Version(3, 14, 14, 12, 12, + new ECBlocks(10, new ECB(1, 8))), + new Version(4, 16, 16, 14, 14, + new ECBlocks(12, new ECB(1, 12))), + new Version(5, 18, 18, 16, 16, + new ECBlocks(14, new ECB(1, 18))), + new Version(6, 20, 20, 18, 18, + new ECBlocks(18, new ECB(1, 22))), + new Version(7, 22, 22, 20, 20, + new ECBlocks(20, new ECB(1, 30))), + new Version(8, 24, 24, 22, 22, + new ECBlocks(24, new ECB(1, 36))), + new Version(9, 26, 26, 24, 24, + new ECBlocks(28, new ECB(1, 44))), + new Version(10, 32, 32, 14, 14, + new ECBlocks(36, new ECB(1, 62))), + new Version(11, 36, 36, 16, 16, + new ECBlocks(42, new ECB(1, 86))), + new Version(12, 40, 40, 18, 18, + new ECBlocks(48, new ECB(1, 114))), + new Version(13, 44, 44, 20, 20, + new ECBlocks(56, new ECB(1, 144))), + new Version(14, 48, 48, 22, 22, + new ECBlocks(68, new ECB(1, 174))), + new Version(15, 52, 52, 24, 24, + new ECBlocks(42, new ECB(2, 102))), + new Version(16, 64, 64, 14, 14, + new ECBlocks(56, new ECB(2, 140))), + new Version(17, 72, 72, 16, 16, + new ECBlocks(36, new ECB(4, 92))), + new Version(18, 80, 80, 18, 18, + new ECBlocks(48, new ECB(4, 114))), + new Version(19, 88, 88, 20, 20, + new ECBlocks(56, new ECB(4, 144))), + new Version(20, 96, 96, 22, 22, + new ECBlocks(68, new ECB(4, 174))), + new Version(21, 104, 104, 24, 24, + new ECBlocks(56, new ECB(6, 136))), + new Version(22, 120, 120, 18, 18, + new ECBlocks(68, new ECB(6, 175))), + new Version(23, 132, 132, 20, 20, + new ECBlocks(62, new ECB(8, 163))), + new Version(24, 144, 144, 22, 22, + new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))), + new Version(25, 8, 18, 6, 16, + new ECBlocks(7, new ECB(1, 5))), + new Version(26, 8, 32, 6, 14, + new ECBlocks(11, new ECB(1, 10))), + new Version(27, 12, 26, 10, 24, + new ECBlocks(14, new ECB(1, 16))), + new Version(28, 12, 36, 10, 16, + new ECBlocks(18, new ECB(1, 22))), + new Version(29, 16, 36, 10, 16, + new ECBlocks(24, new ECB(1, 32))), + new Version(30, 16, 48, 14, 22, + new ECBlocks(28, new ECB(1, 49))) + }; + } + } +} diff --git a/csharp/datamatrix/detector/Detector.cs b/csharp/datamatrix/detector/Detector.cs new file mode 100644 index 000000000..777d1ab52 --- /dev/null +++ b/csharp/datamatrix/detector/Detector.cs @@ -0,0 +1,424 @@ +/* + * Copyright 2008 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.Linq; +using System.Text; +using com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.datamatrix.detector +{ + /** + *

Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code + * is rotated or skewed, or partially obscured.

+ * + * @author Sean Owen + */ + public sealed class Detector + { + private static int MAX_MODULES = 32; + + // Trick to avoid creating new int objects below -- a sort of crude copy of + // the int.valueOf(int) optimization added in Java 5, not in J2ME + private static int[] intS = + {0, 1, 2, 3, 4}; + + private MonochromeBitmapSource image; + + public Detector(MonochromeBitmapSource image) { + this.image = image; + } + + /** + *

Detects a Data Matrix Code in an image.

+ * + * @return {@link DetectorResult} encapsulating results of detecting a QR Code + * @throws ReaderException if no Data Matrix Code can be found + */ + public DetectorResult detect() { + + if (!BlackPointEstimationMethod.TWO_D_SAMPLING.Equals(image.getLastEstimationMethod())) { + image.estimateBlackPoint(BlackPointEstimationMethod.TWO_D_SAMPLING, 0); + } + + int height = image.getHeight(); + int width = image.getWidth(); + int halfHeight = height >> 1; + int halfWidth = width >> 1; + int iSkip = Math.Max(1, height / (MAX_MODULES << 3)); + int jSkip = Math.Max(1, width / (MAX_MODULES << 3)); + + int minI = 0; + int maxI = height; + int minJ = 0; + int maxJ = width; + ResultPoint pointA = findCornerFromCenter(halfHeight, -iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 1); + minI = (int) pointA.getY() - 1; + ResultPoint pointB = findCornerFromCenter(halfHeight, 0, minI, maxI, halfWidth, -jSkip, minJ, maxJ, halfHeight >> 1); + minJ = (int) pointB.getX() - 1; + ResultPoint pointC = findCornerFromCenter(halfHeight, 0, minI, maxI, halfWidth, jSkip, minJ, maxJ, halfHeight >> 1); + maxJ = (int) pointC.getX() + 1; + ResultPoint pointD = findCornerFromCenter(halfHeight, iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 1); + maxI = (int) pointD.getY() + 1; + // Go try to find point A again with better information -- might have been off at first. + pointA = findCornerFromCenter(halfHeight, -iSkip, minI, maxI, halfWidth, 0, minJ, maxJ, halfWidth >> 2); + + // Point A and D are across the diagonal from one another, + // as are B and C. Figure out which are the solid black lines + // by counting transitions + System.Collections.ArrayList transitions = new System.Collections.ArrayList(4); + transitions.Add(transitionsBetween(pointA, pointB)); + transitions.Add(transitionsBetween(pointA, pointC)); + transitions.Add(transitionsBetween(pointB, pointD)); + transitions.Add(transitionsBetween(pointC, pointD)); + Collections.insertionSort(transitions, new ResultPointsAndTransitionsComparator()); + + // Sort by number of transitions. First two will be the two solid sides; last two + // will be the two alternating black/white sides + ResultPointsAndTransitions lSideOne = (ResultPointsAndTransitions) transitions[0]; + ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions) transitions[1]; + + // Figure out which point is their intersection by tallying up the number of times we see the + // endpoints in the four endpoints. One will show up twice. + System.Collections.Hashtable pointCount = new System.Collections.Hashtable(); + increment(pointCount, lSideOne.getFrom()); + increment(pointCount, lSideOne.getTo()); + increment(pointCount, lSideTwo.getFrom()); + increment(pointCount, lSideTwo.getTo()); + + ResultPoint maybeTopLeft = null; + ResultPoint bottomLeft = null; + ResultPoint maybeBottomRight = null; + System.Collections.IEnumerator points = pointCount.GetEnumerator(); + + while (points.MoveNext()) { + ResultPoint point = (ResultPoint) points.Current; + int value = (int) pointCount[point]; + if (value == 2) { + bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides + } else { + // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now + if (maybeTopLeft == null) { + maybeTopLeft = point; + } else { + maybeBottomRight = point; + } + } + } + + if (maybeTopLeft == null || bottomLeft == null || maybeBottomRight == null) { + throw new ReaderException(); + } + + // Bottom left is correct but top left and bottom right might be switched + ResultPoint[] corners = { maybeTopLeft, bottomLeft, maybeBottomRight }; + // Use the dot product trick to sort them out + GenericResultPoint.orderBestPatterns(corners); + + // Now we know which is which: + ResultPoint bottomRight = corners[0]; + bottomLeft = corners[1]; + ResultPoint topLeft = corners[2]; + + // Which point didn't we find in relation to the "L" sides? that's the top right corner + ResultPoint topRight; + if (!pointCount.ContainsKey(pointA)) { + topRight = pointA; + } else if (!pointCount.ContainsKey(pointB)) { + topRight = pointB; + } else if (!pointCount.ContainsKey(pointC)) { + topRight = pointC; + } else { + topRight = pointD; + } + + // Next determine the dimension by tracing along the top or right side and counting black/white + // transitions. Since we start inside a black module, we should see a number of transitions + // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to + // end on a black module: + + // The top right point is actually the corner of a module, which is one of the two black modules + // adjacent to the white module at the top right. Tracing to that corner from either the top left + // or bottom right should work here, but, one will be more reliable since it's traced straight + // up or across, rather than at a slight angle. We use dot products to figure out which is + // better to use: + int dimension; + if (GenericResultPoint.crossProductZ(bottomLeft, bottomRight, topRight) < + GenericResultPoint.crossProductZ(topRight, topLeft, bottomLeft)) { + dimension = transitionsBetween(topLeft, topRight).getTransitions(); + } else { + dimension = transitionsBetween(bottomRight, topRight).getTransitions(); + } + dimension += 2; + + BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, dimension); + return new DetectorResult(bits, new ResultPoint[] {pointA, pointB, pointC, pointD}); + } + + /** + * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center + * point which should be within the barcode. + * + * @param centerI center's i componennt (vertical) + * @param di change in i per step. If scanning up this is negative; down, positive; left or right, 0 + * @param minI minimum value of i to search through (meaningless when di == 0) + * @param maxI maximum value of i + * @param centerJ center's j component (horizontal) + * @param dj same as di but change in j per step instead + * @param minJ see minI + * @param maxJ see minJ + * @param maxWhiteRun maximum run of white pixels that can still be considered to be within + * the barcode + * @return a {@link ResultPoint} encapsulating the corner that was found + * @throws ReaderException if such a point cannot be found + */ + private ResultPoint findCornerFromCenter(int centerI, int di, int minI, int maxI, + int centerJ, int dj, int minJ, int maxJ, + int maxWhiteRun) { + int[] lastRange = null; + for (int i = centerI, j = centerJ; + i < maxI && i >= minI && j < maxJ && j >= minJ; + i += di, j += dj) { + int[] range; + if (dj == 0) { + // horizontal slices, up and down + range = blackWhiteRange(i, maxWhiteRun, minJ, maxJ, true); + } else { + // vertical slices, left and right + range = blackWhiteRange(j, maxWhiteRun, minI, maxI, false); + } + if (range == null) { + if (lastRange == null) { + throw new ReaderException(); + } + // lastRange was found + if (dj == 0) { + int lastI = i - di; + if (lastRange[0] < centerJ) { + if (lastRange[1] > centerJ) { + // straddle, choose one or the other based on direction + return new GenericResultPoint(di > 0 ? lastRange[0] : lastRange[1], lastI); + } + return new GenericResultPoint(lastRange[0], lastI); + } else { + return new GenericResultPoint(lastRange[1], lastI); + } + } else { + int lastJ = j - dj; + if (lastRange[0] < centerI) { + if (lastRange[1] > centerI) { + return new GenericResultPoint(lastJ, dj < 0 ? lastRange[0] : lastRange[1]); + } + return new GenericResultPoint(lastJ, lastRange[0]); + } else { + return new GenericResultPoint(lastJ, lastRange[1]); + } + } + } + lastRange = range; + } + throw new ReaderException(); + } + + /** + * Increments the int associated with a key by one. + */ + private static void increment(System.Collections.Hashtable table, ResultPoint key) { + int value = (int) table[key]; + table[key] = value.Equals(null) ? intS[1] : intS[value + 1]; + //table.put(key, value == null ? intS[1] : intS[value.intValue() + 1]); + } + + /** + * Computes the start and end of a region of pixels, either horizontally or vertically, that could be + * part of a Data Matrix barcode. + * + * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location) where + * we are scanning. If scanning vertically it's the colummn, the fixed horizontal location + * @param maxWhiteRun largest run of white pixels that can still be considered part of the barcode region + * @param minDim minimum pixel location, horizontally or vertically, to consider + * @param maxDim maximum pixel location, horizontally or vertically, to consider + * @param horizontal if true, we're scanning left-right, instead of up-down + * @return int[] with start and end of found range, or null if no such range is found (e.g. only white was found) + */ + private int[] blackWhiteRange(int fixedDimension, int maxWhiteRun, int minDim, int maxDim, bool horizontal) { + + int center = (minDim + maxDim) / 2; + + BitArray rowOrColumn = horizontal ? image.getBlackRow(fixedDimension, null, 0, image.getWidth()) + : image.getBlackColumn(fixedDimension, null, 0, image.getHeight()); + + // Scan left/up first + int start = center; + while (start >= minDim) { + if (rowOrColumn.get(start)) { + start--; + } else { + int whiteRunStart = start; + do { + start--; + } while (start >= minDim && !rowOrColumn.get(start)); + int whiteRunSize = whiteRunStart - start; + if (start < minDim || whiteRunSize > maxWhiteRun) { + start = whiteRunStart + 1; // back up + break; + } + } + } + start++; + + // Then try right/down + int end = center; + while (end < maxDim) { + if (rowOrColumn.get(end)) { + end++; + } else { + int whiteRunStart = end; + do { + end++; + } while (end < maxDim && !rowOrColumn.get(end)); + int whiteRunSize = end - whiteRunStart; + if (end >= maxDim || whiteRunSize > maxWhiteRun) { + end = whiteRunStart - 1; + break; + } + } + } + end--; + + if (end > start) { + return new int[] { start, end }; + } else { + return null; + } + } + + private static BitMatrix sampleGrid(MonochromeBitmapSource image, + ResultPoint topLeft, + ResultPoint bottomLeft, + ResultPoint bottomRight, + int dimension) { + + // We make up the top right point for now, based on the others. + // TODO: we actually found a fourth corner above and figured out which of two modules + // it was the corner of. We could use that here and adjust for perspective distortion. + float topRightX = (bottomRight.getX() - bottomLeft.getX()) + topLeft.getX(); + float topRightY = (bottomRight.getY() - bottomLeft.getY()) + topLeft.getY(); + + // Note that unlike in the QR Code sampler, we didn't find the center of modules, but the + // very corners. So there is no 0.5f here; 0.0f is right. + GridSampler sampler = GridSampler.Instance; + return sampler.sampleGrid( + image, + dimension, + 0.0f, + 0.0f, + dimension, + 0.0f, + dimension, + dimension, + 0.0f, + dimension, + topLeft.getX(), + topLeft.getY(), + topRightX, + topRightY, + bottomRight.getX(), + bottomRight.getY(), + bottomLeft.getX(), + bottomLeft.getY()); + } + + /** + * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm. + */ + private ResultPointsAndTransitions transitionsBetween(ResultPoint from, ResultPoint to) { + // See QR Code Detector, sizeOfBlackWhiteBlackRun() + int fromX = (int) from.getX(); + int fromY = (int) from.getY(); + int toX = (int) to.getX(); + int toY = (int) to.getY(); + bool steep = Math.Abs(toY - fromY) > Math.Abs(toX - fromX); + if (steep) { + int temp = fromX; + fromX = fromY; + fromY = temp; + temp = toX; + toX = toY; + toY = temp; + } + + int dx = Math.Abs(toX - fromX); + int dy = Math.Abs(toY - fromY); + int error = -dx >> 1; + int ystep = fromY < toY ? 1 : -1; + int xstep = fromX < toX ? 1 : -1; + int transitions = 0; + bool inBlack = image.isBlack(steep ? fromY : fromX, steep ? fromX : fromY); + for (int x = fromX, y = fromY; x != toX; x += xstep) { + bool isBlack = image.isBlack(steep ? y : x, steep ? x : y); + if (isBlack == !inBlack) { + transitions++; + inBlack = isBlack; + } + error += dy; + if (error > 0) { + y += ystep; + error -= dx; + } + } + return new ResultPointsAndTransitions(from, to, transitions); + } + + /** + * Simply encapsulates two points and a number of transitions between them. + */ + private class ResultPointsAndTransitions { + private ResultPoint from; + private ResultPoint to; + private int transitions; + + public ResultPointsAndTransitions(ResultPoint from, ResultPoint to, int transitions) { + this.from = from; + this.to = to; + this.transitions = transitions; + } + + public ResultPoint getFrom() { + return from; + } + public ResultPoint getTo() { + return to; + } + public int getTransitions() { + return transitions; + } + public String toString() { + return from + "/" + to + '/' + transitions; + } + } + + /** + * Orders ResultPointsAndTransitions by number of transitions, ascending. + */ + private class ResultPointsAndTransitionsComparator : Comparator { + public int compare(Object o1, Object o2) { + return ((ResultPointsAndTransitions) o1).getTransitions() - ((ResultPointsAndTransitions) o2).getTransitions(); + } + } + } +} diff --git a/csharp/obj/Release/com.google.zxing.dll b/csharp/obj/Release/com.google.zxing.dll new file mode 100644 index 000000000..107727e17 Binary files /dev/null and b/csharp/obj/Release/com.google.zxing.dll differ diff --git a/csharp/obj/Release/com.google.zxing.pdb b/csharp/obj/Release/com.google.zxing.pdb new file mode 100644 index 000000000..3aef4741f Binary files /dev/null and b/csharp/obj/Release/com.google.zxing.pdb differ diff --git a/csharp/obj/Release/zxing.csproj.FileListAbsolute.txt b/csharp/obj/Release/zxing.csproj.FileListAbsolute.txt new file mode 100644 index 000000000..2d04eedfc --- /dev/null +++ b/csharp/obj/Release/zxing.csproj.FileListAbsolute.txt @@ -0,0 +1,5 @@ +C:\projects\my open source project\zxingsharp\obj\Release\ResolveAssemblyReference.cache +C:\projects\my open source project\zxingsharp\bin\Release\com.google.zxing.dll +C:\projects\my open source project\zxingsharp\bin\Release\com.google.zxing.pdb +C:\projects\my open source project\zxingsharp\obj\Release\com.google.zxing.dll +C:\projects\my open source project\zxingsharp\obj\Release\com.google.zxing.pdb diff --git a/csharp/zxing.csproj b/csharp/zxing.csproj index 3bebda4c6..179dfccd6 100755 --- a/csharp/zxing.csproj +++ b/csharp/zxing.csproj @@ -71,6 +71,13 @@ + + + + + + + diff --git a/csharp/zxing.suo b/csharp/zxing.suo new file mode 100644 index 000000000..c662b2b9a Binary files /dev/null and b/csharp/zxing.suo differ