diff --git a/AUTHORS b/AUTHORS index 415fdf752..2a81be8cf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ Joseph Wain (Google) Kevin O'Sullivan (SITA) Matthew Schulkind (Google) Matt York (LifeMarks) +Mohamad Fairol Paul Hackenberger sanfordsquires (?) Sean Owen (Google) diff --git a/csharp/BarcodeFormat.cs b/csharp/BarcodeFormat.cs new file mode 100755 index 000000000..3cdf6bf93 --- /dev/null +++ b/csharp/BarcodeFormat.cs @@ -0,0 +1,89 @@ +/* +* 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. +*/ +namespace com.google.zxing +{ + using System; + + /// Enumerates barcode formats known to this package. + /// * + /// + /// Sean Owen + /// + /// + public sealed class BarcodeFormat + { + + // No, we can't use an enum here. J2ME doesn't support it. + + /// QR Code 2D barcode format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'QR_CODE '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat QR_CODE = new BarcodeFormat("QR_CODE"); + + /// DataMatrix 2D barcode format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'DATAMATRIX '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat DATAMATRIX = new BarcodeFormat("DATAMATRIX"); + + /// UPC-E 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'UPC_E '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat UPC_E = new BarcodeFormat("UPC_E"); + + /// UPC-A 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'UPC_A '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat UPC_A = new BarcodeFormat("UPC_A"); + + /// EAN-8 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'EAN_8 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat EAN_8 = new BarcodeFormat("EAN_8"); + + /// EAN-13 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'EAN_13 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat EAN_13 = new BarcodeFormat("EAN_13"); + + /// Code 128 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'CODE_128 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat CODE_128 = new BarcodeFormat("CODE_128"); + + /// Code 39 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'CODE_39 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat CODE_39 = new BarcodeFormat("CODE_39"); + + /// ITF (Interleaved Two of Five) 1D format. + /// + //UPGRADE_NOTE: Final was removed from the declaration of 'ITF '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + public static readonly BarcodeFormat ITF = new BarcodeFormat("ITF"); + + //UPGRADE_NOTE: Final was removed from the declaration of 'name '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + private System.String name; + + private BarcodeFormat(System.String name) + { + this.name = name; + } + + public override System.String ToString() + { + return name; + } + } +} \ No newline at end of file diff --git a/csharp/BlackPointEstimationMethod.cs b/csharp/BlackPointEstimationMethod.cs new file mode 100755 index 000000000..5217afe1d --- /dev/null +++ b/csharp/BlackPointEstimationMethod.cs @@ -0,0 +1,39 @@ +/* +* 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; +namespace com.google.zxing +{ + + ///

Enumerates different methods of sampling an imagine to estimate a black point.

+ /// + ///
+ /// srowen@google.com (Sean Owen), dswitkin@google.com (Daniel Switkin) + /// + public sealed class BlackPointEstimationMethod + { + /** + * Method probably most suitable for use with 2D barcdoe format. + */ + public static BlackPointEstimationMethod TWO_D_SAMPLING = new BlackPointEstimationMethod(); + /** + * Method probably most suitable for 1D barcode decoding, where one row at a time is sampled. + */ + public static BlackPointEstimationMethod ROW_SAMPLING = new BlackPointEstimationMethod(); + + private BlackPointEstimationMethod() { + // do nothing + } + } +} \ No newline at end of file diff --git a/csharp/BufferedImageMonochromeBitmapSource.cs b/csharp/BufferedImageMonochromeBitmapSource.cs new file mode 100755 index 000000000..587102275 --- /dev/null +++ b/csharp/BufferedImageMonochromeBitmapSource.cs @@ -0,0 +1,181 @@ +/* +* 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.Drawing; +using MonochromeBitmapSource = com.google.zxing.MonochromeBitmapSource; +using BlackPointEstimationMethod = com.google.zxing.BlackPointEstimationMethod; +using BitArray = com.google.zxing.common.BitArray; +using BlackPointEstimator = com.google.zxing.common.BlackPointEstimator; + + +namespace com.google.zxing.client.j2se +{ + + ///

An implementation based upon {@link BufferedImage}. This provides access to the + /// underlying image as if it were a monochrome image. Behind the scenes, it is evaluating + /// the luminance of the underlying image by retrieving its pixels' RGB values.

+ /// + ///
+ /// srowen@google.com (Sean Owen), Daniel Switkin (dswitkin@google.com) + /// + public sealed class BufferedImageMonochromeBitmapSource : MonochromeBitmapSource + { + public bool iRotateSupported = false; + + public bool isRotateSupported() { + return iRotateSupported; + } + + public int getWidth() { + return (iRotateSupported ? image.Height : image.Width); + } + + public BlackPointEstimationMethod getLastEstimationMethod() { + return lastMethod; + } + + public int getHeight() + { + return (iRotateSupported ? image.Width : image.Height); + } + + + public MonochromeBitmapSource rotateCounterClockwise() { + return null; + } + + public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight) { + return null; + } + + //UPGRADE_NOTE: Final was removed from the declaration of 'image '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private System.Drawing.Bitmap image; + private int blackPoint; + private BlackPointEstimationMethod lastMethod; + private int lastArgument; + + private const int LUMINANCE_BITS = 5; + //UPGRADE_NOTE: Final was removed from the declaration of 'LUMINANCE_SHIFT '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private static readonly int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; + //UPGRADE_NOTE: Final was removed from the declaration of 'LUMINANCE_BUCKETS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private static readonly int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + + public BufferedImageMonochromeBitmapSource(System.Drawing.Bitmap image, bool rotated) + { + this.image = image; + blackPoint = 0x7F; + lastMethod = null; + lastArgument = 0; + iRotateSupported = rotated; + } + + public bool isBlack(int x, int y) + { + return (iRotateSupported ? computeRGBLuminance(image.GetPixel(y, x).ToArgb()) < blackPoint : computeRGBLuminance(image.GetPixel(x, y).ToArgb()) < blackPoint); + } + + int[] getRGB(int startx, int starty, int width) + { + int[] pixels = new int[width]; + for (int k = 0; k < width; k++) + { + Color c = (iRotateSupported ? image.GetPixel(starty, startx + k) : image.GetPixel(startx + k, starty)); + pixels[k] = ((int)c.R) << 16 | ((int)c.G) << 8 | ((int)c.B); + } + + return pixels; + } + + public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth) + { + if (row == null) + { + row = new BitArray(getWidth); + } + else + { + row.clear(); + } + //UPGRADE_ISSUE: Method 'java.awt.image.BufferedImage.getRGB' was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1000_javaawtimageBufferedImagegetRGB_int_int_int_int_int[]_int_int'" + int[] pixelRow = getRGB(startX, y, getWidth); + for (int i = 0; i < getWidth; i++) + { + if (computeRGBLuminance(pixelRow[i]) < blackPoint) + { + row.set(i); + } + } + return row; + } + + public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) + { + if (!method.Equals(lastMethod) || argument != lastArgument) + { + int width = getWidth(); + int height = getHeight(); + int[] histogram = new int[LUMINANCE_BUCKETS]; + float biasTowardsWhite = 1.0f; + if (method.Equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) + { + int minDimension = width < height?width:height; + int startI = height == minDimension?0:(height - width) >> 1; + int startJ = width == minDimension?0:(width - height) >> 1; + for (int n = 0; n < minDimension; n++) + { + int pixel = (iRotateSupported ? image.GetPixel(startI + n, startJ + n).ToArgb() : image.GetPixel(startJ + n, startI + n).ToArgb()); + histogram[computeRGBLuminance(pixel) >> LUMINANCE_SHIFT]++; + } + } + else if (method.Equals(BlackPointEstimationMethod.ROW_SAMPLING)) + { + if (argument < 0 || argument >= height) + { + throw new System.ArgumentException("Row is not within the image: " + argument); + } + biasTowardsWhite = 2.0f; + int[] rgbArray = getRGB(0, argument, width); + for (int x = 0; x < width; x++) + { + int l = computeRGBLuminance(rgbArray[x]); + histogram[l >> LUMINANCE_SHIFT]++; + } + } + else + { + //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Object.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'" + throw new System.ArgumentException("Unknown method: " + method); + } + blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; + lastMethod = method; + lastArgument = argument; + } + } + + /// Extracts luminance from a pixel from this source. By default, the source is assumed to use RGB, + /// so this implementation computes luminance is a function of a red, green and blue components as + /// follows: + /// + /// Y = 0.299R + 0.587G + 0.114B + /// + /// where R, G, and B are values in [0,1]. + /// + private static int computeRGBLuminance(int pixel) + { + // Coefficients add up to 1024 to make the divide into a fast shift + return (306 * ((pixel >> 16) & 0xFF) + 601 * ((pixel >> 8) & 0xFF) + 117 * (pixel & 0xFF)) >> 10; + } + } +} \ No newline at end of file diff --git a/csharp/DecodeHintType.cs b/csharp/DecodeHintType.cs new file mode 100755 index 000000000..8bc319075 --- /dev/null +++ b/csharp/DecodeHintType.cs @@ -0,0 +1,63 @@ +/* +* 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. +*/ +namespace com.google.zxing +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class DecodeHintType + { + // No, we can't use an enum here. J2ME doesn't support it. + /** + * Unspecified, application-specific hint. Maps to an unspecified {@link Object}. + */ + public static DecodeHintType OTHER = new DecodeHintType(); + + /** + * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to; + * use {@link Boolean#TRUE}. + */ + public static DecodeHintType PURE_BARCODE = new DecodeHintType(); + + /** + * Image is known to be of one of a few possible formats. + * Maps to a {@link java.util.Vector} of {@link BarcodeFormat}s. + */ + public static DecodeHintType POSSIBLE_FORMATS = new DecodeHintType(); + + /** + * Spend more time to try to find a barcode; optimize for accuracy, not speed. + * Doesn't matter what it maps to; use {@link Boolean#TRUE}. + */ + public static DecodeHintType TRY_HARDER = new DecodeHintType(); + + private DecodeHintType() { + } + + } +} \ No newline at end of file diff --git a/csharp/EncodeHintType.cs b/csharp/EncodeHintType.cs new file mode 100755 index 000000000..3ee58a12d --- /dev/null +++ b/csharp/EncodeHintType.cs @@ -0,0 +1,44 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class EncodeHintType + { + /** + * Specifies what degree of error correction to use, for example in QR Codes (type Integer). + */ + public static EncodeHintType ERROR_CORRECTION = new EncodeHintType(); + + private EncodeHintType() { + } + + } +} \ No newline at end of file diff --git a/csharp/MonochromeBitmapSource.cs b/csharp/MonochromeBitmapSource.cs new file mode 100755 index 000000000..58dd98524 --- /dev/null +++ b/csharp/MonochromeBitmapSource.cs @@ -0,0 +1,104 @@ +/* +* 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 BitArray = com.google.zxing.common.BitArray; +namespace com.google.zxing +{ + + ///

Encapsulates a generic black-and-white bitmap -- a collection of pixels in two dimensions. + /// This unifies many possible representations, like AWT's BufferedImage.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + public interface MonochromeBitmapSource + { + /** + * @param x horizontal offset, from left, of the pixel + * @param y vertical offset, from top, of the pixel + * @return true iff the pixel at (x,y) is black + */ + bool isBlack(int x, int y); + + /** + *

Returns an entire row of black/white pixels as an array of bits, where "true" means "black". + * This is a sort of "bulk get" operation intended to enable efficient access in + * certain situations.

+ * + * @param y vertical offset, from top, of the row of pixels + * @param row if not null, {@link BitArray} to write pixels into. If null, a new {@link BitArray} + * is allocated and returned. + * @param startX horizontal offset, from left, from which to start getting pixels + * @param getWidth number of pixels to get from the row + * @return {@link BitArray} representing the (subset of the) row of pixels. If row parameter + * was not null, it is returned. + */ + BitArray getBlackRow(int y, BitArray row, int startX, int getWidth); + + /** + * Entirely analogous to {@link #getBlackRow(int, BitArray, int, int)} but gets a column. + */ + BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight); + + /** + * @return height of underlying image + */ + int getHeight(); + + /** + * @return width of underlying image + */ + int getWidth(); + + /** + *

Estimates black point according to the given method, which is optionally parameterized by + * a single int argument. For {@link BlackPointEstimationMethod#ROW_SAMPLING}, this + * specifies the row to sample.

+ * + *

The estimated value will be used in subsequent computations that rely on an estimated black + * point.

+ * + * @param method black point estimation method + * @param argument method-specific argument + */ + void estimateBlackPoint(BlackPointEstimationMethod method, int argument); + + /** + * @return {@link BlackPointEstimationMethod} representing last sampling method used + */ + BlackPointEstimationMethod getLastEstimationMethod(); + + /** + *

Optional operation which returns an implementation based on the same underlying + * image, but which behaves as if the underlying image had been rotated 90 degrees + * counterclockwise. This is useful in the context of 1D barcodes and the + * {@link DecodeHintType#TRY_HARDER} decode hint, and is only intended to be + * used in non-resource-constrained environments. Hence, implementations + * of this class which are only used in resource-constrained mobile environments + * don't have a need to implement this.

+ * + * @throws IllegalArgumentException if not supported + */ + MonochromeBitmapSource rotateCounterClockwise(); + + /** + * @return true iff rotation is supported + * @see #rotateCounterClockwise() + */ + bool isRotateSupported(); + + + } +} \ No newline at end of file diff --git a/csharp/MultiFormatReader.cs b/csharp/MultiFormatReader.cs new file mode 100755 index 000000000..0fed7777f --- /dev/null +++ b/csharp/MultiFormatReader.cs @@ -0,0 +1,169 @@ +/* +* 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; +using com.google.zxing.qrcode; + +namespace com.google.zxing +{ + public sealed class MultiFormatReader : Reader + { + private Hashtable hints; + private ArrayList readers; + + /** + * This version of decode honors the intent of Reader.decode(MonochromeBitmapSource) in that it + * passes null as a hint to the decoders. However, that makes it inefficient to call repeatedly. + * Use setHints() followed by decodeWithState() for continuous scan applications. + * + * @param image The pixel data to decode + * @return The contents of the image + * @throws ReaderException Any errors which occurred + */ + public Result decode(MonochromeBitmapSource image){ + try{ + setHints(null); + return decodeInternal(image); + } + catch(Exception e){ + throw new ReaderException(e.Message); + } + } + + /** + * Decode an image using the hints provided. Does not honor existing state. + * + * @param image The pixel data to decode + * @param hints The hints to use, clearing the previous state. + * @return The contents of the image + * @throws ReaderException Any errors which occurred + */ + public Result decode(MonochromeBitmapSource image, Hashtable hints){ + try{ + setHints(hints); + return decodeInternal(image); + }catch(Exception e){ + throw new ReaderException (e.Message); + } + } + + /** + * Decode an image using the state set up by calling setHints() previously. Continuous scan + * clients will get a large speed increase by using this instead of decode(). + * + * @param image The pixel data to decode + * @return The contents of the image + * @throws ReaderException Any errors which occurred + */ + public Result decodeWithState(MonochromeBitmapSource image){ + try{ + // Make sure to set up the default state so we don't crash + if (readers == null) { + setHints(null); + } + return decodeInternal(image); + }catch(Exception e){ + throw new ReaderException(e.Message); + } + } + + /** + * This method adds state to the MultiFormatReader. By setting the hints once, subsequent calls + * to decodeWithState(image) can reuse the same set of readers without reallocating memory. This + * is important for performance in continuous scan clients. + * + * @param hints The set of hints to use for subsequent calls to decode(image) + */ + public void setHints(Hashtable hints) { + this.hints = hints; + + bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER); + + ArrayList possibleFormats = hints == null ? null : (ArrayList)hints[(DecodeHintType.POSSIBLE_FORMATS)]; + readers = new ArrayList(); + if (possibleFormats != null) + { + bool addOneDReader = + possibleFormats.Contains(BarcodeFormat.UPC_A) || + possibleFormats.Contains(BarcodeFormat.UPC_E) || + possibleFormats.Contains(BarcodeFormat.EAN_13) || + possibleFormats.Contains(BarcodeFormat.EAN_8) || + possibleFormats.Contains(BarcodeFormat.CODE_39) || + possibleFormats.Contains(BarcodeFormat.CODE_128); + // Put 1D readers upfront in "normal" mode + + if (addOneDReader && !tryHarder) + { + //readers.Add(new MultiFormatOneDReader(hints)); + } + + if (possibleFormats.Contains(BarcodeFormat.QR_CODE)) + { + readers.Add(new QRCodeReader()); + } + // TODO re-enable once Data Matrix is ready + //if (possibleFormats.contains(BarcodeFormat.DATAMATRIX)) { + // readers.addElement(new DataMatrixReader()); + //} + // At end in "try harder" mode + if (addOneDReader && tryHarder) + { + //readers.Add(new MultiFormatOneDReader(hints)); + } + } + + if (readers.Count == 0) + { + if (!tryHarder) + { + //readers.Add(new MultiFormatOneDReader(hints)); + } + readers.Add(new QRCodeReader()); + // TODO re-enable once Data Matrix is ready + // readers.addElement(new DataMatrixReader()); + if (tryHarder) + { + //readers.Add(new MultiFormatOneDReader(hints)); + } + } + } + + private Result decodeInternal(MonochromeBitmapSource image) { + try + { + int size = readers.Count; + for (int i = 0; i < size; i++) + { + Reader reader = (Reader)readers[i]; + try + { + return reader.decode(image, hints); + } + catch (ReaderException re) + { + // continue + } + } + + throw new ReaderException(""); + } + catch (Exception e) { + throw new ReaderException(e.Message); + } + } + + + } +} \ No newline at end of file diff --git a/csharp/MultiFormatWriter.cs b/csharp/MultiFormatWriter.cs new file mode 100755 index 000000000..42f244cda --- /dev/null +++ b/csharp/MultiFormatWriter.cs @@ -0,0 +1,39 @@ +/* +* 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; +using com.google.zxing.qrcode; +using com.google.zxing.common; + +namespace com.google.zxing +{ + public sealed class MultiFormatWriter : Writer + { + public ByteMatrix encode(String contents, BarcodeFormat format, int width,int height) { + return encode(contents, format, width, height,null); + } + + public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height,Hashtable hints){ + if (format == BarcodeFormat.QR_CODE) { + return new QRCodeWriter().encode(contents, format, width, height, hints); + } else { + throw new ArgumentException("No encoder available for format " + format); + } + } + } +} + + + diff --git a/csharp/Properties/AssemblyInfo.cs b/csharp/Properties/AssemblyInfo.cs new file mode 100755 index 000000000..672bae1e0 --- /dev/null +++ b/csharp/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("QRCodeLib3")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("QRCodeLib3")] +[assembly: AssemblyCopyright("Copyright © 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("869eed67-0f3d-424a-8e40-d8bb61c8dc7b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/csharp/README b/csharp/README new file mode 100644 index 000000000..49dcb93d9 --- /dev/null +++ b/csharp/README @@ -0,0 +1 @@ +This port was contributed and is maintained by Mohamad Fairol. \ No newline at end of file diff --git a/csharp/Reader.cs b/csharp/Reader.cs new file mode 100755 index 000000000..631cab861 --- /dev/null +++ b/csharp/Reader.cs @@ -0,0 +1,45 @@ +/* +* 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; + +namespace com.google.zxing +{ + public interface Reader + { + /** + * Locates and decodes a barcode in some format within an image. + * + * @param image image of barcode to decode + * @return String which the barcode encodes + * @throws ReaderException if the barcode cannot be located or decoded for any reason + */ + Result decode(MonochromeBitmapSource image); + + /** + * Locates and decodes a barcode in some format within an image. This method also accepts + * hints, each possibly associated to some data, which may help the implementation decode. + * + * @param image image of barcode to decode + * @param hints passed as a {@link Hashtable} from {@link DecodeHintType} to aribtrary data. The + * meaning of the data depends upon the hint type. The implementation may or may not do + * anything with these hints. + * @return String which the barcode encodes + * @throws ReaderException if the barcode cannot be located or decoded for any reason + */ + Result decode(MonochromeBitmapSource image, Hashtable hints); + } + +} \ No newline at end of file diff --git a/csharp/ReaderException.cs b/csharp/ReaderException.cs new file mode 100755 index 000000000..c84928a4f --- /dev/null +++ b/csharp/ReaderException.cs @@ -0,0 +1,40 @@ +/* +* 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; +namespace com.google.zxing +{ + + /// The general exception class throw when something goes wrong during decoding of a barcode. + /// This includes, but is not limited to, failing checksums / error correction algorithms, being + /// unable to locate finder timing patterns, and so on. + /// + /// + /// srowen@google.com (Sean Owen) + /// + //[Serializable] + public sealed class ReaderException : System.Exception + { + + private static ReaderException instance = new ReaderException(); + + public ReaderException() { + // do nothing + } + + public ReaderException(System.String message): base(message) + { + } + } +} \ No newline at end of file diff --git a/csharp/Result.cs b/csharp/Result.cs new file mode 100755 index 000000000..1bc5841e7 --- /dev/null +++ b/csharp/Result.cs @@ -0,0 +1,106 @@ +/* +* 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; + +namespace com.google.zxing +{ + + /// The general exception class throw when something goes wrong during decoding of a barcode. + /// This includes, but is not limited to, failing checksums / error correction algorithms, being + /// unable to locate finder timing patterns, and so on. + /// + /// + /// srowen@google.com (Sean Owen) + /// + //[Serializable] + public sealed class Result + { + private String text; + private sbyte[] rawBytes; + private ResultPoint[] resultPoints; + private BarcodeFormat format; + private Hashtable resultMetadata; + + public Result(String text, + sbyte[] rawBytes, + ResultPoint[] resultPoints, + BarcodeFormat format) { + if (text == null && rawBytes == null) { + throw new ArgumentException("Text and bytes are null"); + } + this.text = text; + this.rawBytes = rawBytes; + this.resultPoints = resultPoints; + this.format = format; + this.resultMetadata = null; + } + + /** + * @return raw text encoded by the barcode, if applicable, otherwise null + */ + public String getText() { + return text; + } + + /** + * @return raw bytes encoded by the barcode, if applicable, otherwise null + */ + public sbyte[] getRawBytes() { + return rawBytes; + } + + /** + * @return points related to the barcode in the image. These are typically points + * identifying finder patterns or the corners of the barcode. The exact meaning is + * specific to the type of barcode that was decoded. + */ + public ResultPoint[] getResultPoints() { + return resultPoints; + } + + /** + * @return {@link BarcodeFormat} representing the format of the barcode that was recognized and decoded + */ + public BarcodeFormat getBarcodeFormat() { + return format; + } + + /** + * @return {@link Hashtable} mapping {@link ResultMetadataType} keys to values. May be null. + * This contains optional metadata about what was detected about the barcode, like orientation. + */ + public Hashtable getResultMetadata() { + return resultMetadata; + } + + public void putMetadata(ResultMetadataType type, Object value) { + if (resultMetadata == null) { + resultMetadata = new Hashtable(3); + } + resultMetadata.Add(type, value); + } + + public String toString() { + if (text == null) { + return "[" + rawBytes.Length + " bytes]"; + } else { + return text; + } + } + + + } +} \ No newline at end of file diff --git a/csharp/ResultMetadataType.cs b/csharp/ResultMetadataType.cs new file mode 100755 index 000000000..5a371300f --- /dev/null +++ b/csharp/ResultMetadataType.cs @@ -0,0 +1,63 @@ +/* +* 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; + +namespace com.google.zxing +{ + + /// The general exception class throw when something goes wrong during decoding of a barcode. + /// This includes, but is not limited to, failing checksums / error correction algorithms, being + /// unable to locate finder timing patterns, and so on. + /// + /// + /// srowen@google.com (Sean Owen) + /// + //[Serializable] + public sealed class ResultMetadataType + { + // No, we can't use an enum here. J2ME doesn't support it. + + /** + * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}. + */ + public static ResultMetadataType OTHER = new ResultMetadataType(); + + /** + * Denotes the likely approximate orientation of the barcode in the image. This value + * is given as degrees rotated clockwise from the normal, upright orientation. + * For example a 1D barcode which was found by reading top-to-bottom would be + * said to have orientation "90". This key maps to an {@link Integer} whose + * value is in the range [0,360). + */ + public static ResultMetadataType ORIENTATION = new ResultMetadataType(); + + /** + *

2D barcode formats typically encode text, but allow for a sort of 'byte mode' + * which is sometimes used to encode binary data. While {@link Result} makes available + * the complete raw bytes in the barcode for these formats, it does not offer the bytes + * from the byte segments alone.

+ * + *

This maps to a {@link java.util.Vector} of byte arrays corresponding to the + * raw bytes in the byte segments in the barcode, in order.

+ */ + public static ResultMetadataType BYTE_SEGMENTS = new ResultMetadataType(); + + private ResultMetadataType() { + } + + + } +} \ No newline at end of file diff --git a/csharp/ResultPoint.cs b/csharp/ResultPoint.cs new file mode 100755 index 000000000..378c0cd67 --- /dev/null +++ b/csharp/ResultPoint.cs @@ -0,0 +1,38 @@ +/* +* 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. +*/ +namespace com.google.zxing +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public interface ResultPoint + { + float getX(); + float getY(); + } +} \ No newline at end of file diff --git a/csharp/SupportClass.cs b/csharp/SupportClass.cs new file mode 100755 index 000000000..5058d7d3c --- /dev/null +++ b/csharp/SupportClass.cs @@ -0,0 +1,456 @@ +/* +* 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; + + /// + /// This interface should be implemented by any class whose instances are intended + /// to be executed by a thread. + /// + public interface IThreadRunnable + { + /// + /// This method has to be implemented in order that starting of the thread causes the object's + /// run method to be called in that separately executing thread. + /// + void Run(); + } + +/// +/// Contains conversion support elements such as classes, interfaces and static methods. +/// +public class SupportClass +{ + + + + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static int URShift(int number, int bits) + { + if ( number >= 0) + return number >> bits; + else + return (number >> bits) + (2 << ~bits); + } + + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static int URShift(int number, long bits) + { + return URShift(number, (int)bits); + } + + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static long URShift(long number, int bits) + { + if ( number >= 0) + return number >> bits; + else + return (number >> bits) + (2L << ~bits); + } + + /// + /// Performs an unsigned bitwise right shift with the specified number + /// + /// Number to operate on + /// Ammount of bits to shift + /// The resulting number from the shift operation + public static long URShift(long number, long bits) + { + return URShift(number, (int)bits); + } + + /*******************************/ + /// + /// Copies an array of chars obtained from a String into a specified array of chars + /// + /// The String to get the chars from + /// Position of the String to start getting the chars + /// Position of the String to end getting the chars + /// Array to return the chars + /// Position of the destination array of chars to start storing the chars + /// An array of chars + public static void GetCharsFromString(System.String sourceString, int sourceStart, int sourceEnd, char[] destinationArray, int destinationStart) + { + int sourceCounter; + int destinationCounter; + sourceCounter = sourceStart; + destinationCounter = destinationStart; + while (sourceCounter < sourceEnd) + { + destinationArray[destinationCounter] = (char) sourceString[sourceCounter]; + sourceCounter++; + destinationCounter++; + } + } + + /*******************************/ + /// + /// Converts an array of sbytes to an array of bytes + /// + /// The array of sbytes to be converted + /// The new array of bytes + public static byte[] ToByteArray(sbyte[] sbyteArray) + { + byte[] byteArray = null; + + if (sbyteArray != null) + { + byteArray = new byte[sbyteArray.Length]; + for(int index=0; index < sbyteArray.Length; index++) + byteArray[index] = (byte) sbyteArray[index]; + } + return byteArray; + } + + /// + /// Converts a string to an array of bytes + /// + /// The string to be converted + /// The new array of bytes + public static byte[] ToByteArray(System.String sourceString) + { + return System.Text.UTF8Encoding.UTF8.GetBytes(sourceString); + } + + /// + /// Converts a array of object-type instances to a byte-type array. + /// + /// Array to convert. + /// An array of byte type elements. + public static byte[] ToByteArray(System.Object[] tempObjectArray) + { + byte[] byteArray = null; + if (tempObjectArray != null) + { + byteArray = new byte[tempObjectArray.Length]; + for (int index = 0; index < tempObjectArray.Length; index++) + byteArray[index] = (byte)tempObjectArray[index]; + } + return byteArray; + } + + /*******************************/ + /// + /// Sets the capacity for the specified ArrayList + /// + /// The ArrayList which capacity will be set + /// The new capacity value + public static void SetCapacity(System.Collections.ArrayList vector, int newCapacity) + { + if (newCapacity > vector.Count) + vector.AddRange(new Array[newCapacity-vector.Count]); + else if (newCapacity < vector.Count) + vector.RemoveRange(newCapacity, vector.Count - newCapacity); + vector.Capacity = newCapacity; + } + + + + /*******************************/ + /// + /// This method returns the literal value received + /// + /// The literal to return + /// The received value + public static long Identity(long literal) + { + return literal; + } + + /// + /// This method returns the literal value received + /// + /// The literal to return + /// The received value + public static ulong Identity(ulong literal) + { + return literal; + } + + /// + /// This method returns the literal value received + /// + /// The literal to return + /// The received value + public static float Identity(float literal) + { + return literal; + } + + /// + /// This method returns the literal value received + /// + /// The literal to return + /// The received value + public static double Identity(double literal) + { + return literal; + } + + /*******************************/ + /// + /// Support class used to handle threads + /// + public class ThreadClass : IThreadRunnable + { + /// + /// The instance of System.Threading.Thread + /// + private System.Threading.Thread threadField; + + /// + /// Initializes a new instance of the ThreadClass class + /// + public ThreadClass() + { + threadField = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); + } + + /// + /// Initializes a new instance of the Thread class. + /// + /// The name of the thread + public ThreadClass(System.String Name) + { + threadField = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); + this.Name = Name; + } + + /// + /// Initializes a new instance of the Thread class. + /// + /// A ThreadStart delegate that references the methods to be invoked when this thread begins executing + public ThreadClass(System.Threading.ThreadStart Start) + { + threadField = new System.Threading.Thread(Start); + } + + /// + /// Initializes a new instance of the Thread class. + /// + /// A ThreadStart delegate that references the methods to be invoked when this thread begins executing + /// The name of the thread + public ThreadClass(System.Threading.ThreadStart Start, System.String Name) + { + threadField = new System.Threading.Thread(Start); + this.Name = Name; + } + + /// + /// This method has no functionality unless the method is overridden + /// + public virtual void Run() + { + } + + /// + /// Causes the operating system to change the state of the current thread instance to ThreadState.Running + /// + public virtual void Start() + { + threadField.Start(); + } + + ///// + ///// Interrupts a thread that is in the WaitSleepJoin thread state + ///// + //public virtual void Interrupt() + //{ + // threadField.Interrupt(); + //} + + /// + /// Gets the current thread instance + /// + public System.Threading.Thread Instance + { + get + { + return threadField; + } + set + { + threadField = value; + } + } + + /// + /// Gets or sets the name of the thread + /// + public System.String Name + { + get + { + return threadField.Name; + } + set + { + if (threadField.Name == null) + threadField.Name = value; + } + } + + /// + /// Gets or sets a value indicating the scheduling priority of a thread + /// + public System.Threading.ThreadPriority Priority + { + get + { + return threadField.Priority; + } + set + { + threadField.Priority = value; + } + } + + ///// + ///// Gets a value indicating the execution status of the current thread + ///// + //public bool IsAlive + //{ + // get + // { + // return threadField.IsAlive; + // } + //} + + /// + /// Gets or sets a value indicating whether or not a thread is a background thread. + /// + public bool IsBackground + { + get + { + return threadField.IsBackground; + } + set + { + threadField.IsBackground = value; + } + } + + /// + /// Blocks the calling thread until a thread terminates + /// + public void Join() + { + threadField.Join(); + } + + /// + /// Blocks the calling thread until a thread terminates or the specified time elapses + /// + /// Time of wait in milliseconds + public void Join(int MiliSeconds) + { + lock(this) + { + threadField.Join(MiliSeconds); + } + } + + ///// + ///// Blocks the calling thread until a thread terminates or the specified time elapses + ///// + ///// Time of wait in milliseconds + ///// Time of wait in nanoseconds + //public void Join(long MiliSeconds, int NanoSeconds) + //{ + // lock(this) + // { + // threadField.Join(new System.TimeSpan(MiliSeconds * 10000 + NanoSeconds * 100)); + // } + //} + + ///// + ///// Resumes a thread that has been suspended + ///// + //public void Resume() + //{ + // threadField.Resume(); + //} + + /// + /// Raises a ThreadAbortException in the thread on which it is invoked, + /// to begin the process of terminating the thread. Calling this method + /// usually terminates the thread + /// + public void Abort() + { + threadField.Abort(); + } + + /// + /// Raises a ThreadAbortException in the thread on which it is invoked, + /// to begin the process of terminating the thread while also providing + /// exception information about the thread termination. + /// Calling this method usually terminates the thread. + /// + /// An object that contains application-specific information, such as state, which can be used by the thread being aborted + public void Abort(System.Object stateInfo) + { + lock(this) + { + threadField.Abort(stateInfo); + } + } + + ///// + ///// Suspends the thread, if the thread is already suspended it has no effect + ///// + //public void Suspend() + //{ + // threadField.Suspend(); + //} + + /// + /// Obtain a String that represents the current Object + /// + /// A String that represents the current Object + public override System.String ToString() + { + return "Thread[" + Name + "," + Priority.ToString() + "," + "" + "]"; + } + + /// + /// Gets the currently running thread + /// + /// The currently running thread + public static ThreadClass Current() + { + ThreadClass CurrentThread = new ThreadClass(); + CurrentThread.Instance = System.Threading.Thread.CurrentThread; + return CurrentThread; + } + } + + +} diff --git a/csharp/Writer.cs b/csharp/Writer.cs new file mode 100755 index 000000000..dcba9ecf0 --- /dev/null +++ b/csharp/Writer.cs @@ -0,0 +1,60 @@ +/* +* 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. +*/ +namespace com.google.zxing +{ + using System; + using ByteMatrix = com.google.zxing.common.ByteMatrix; + /// The base class for all objects which encode/generate a barcode image. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public interface Writer + { + /// Encode a barcode using the default settings. + /// * + /// + /// The contents to encode in the barcode + /// + /// The barcode format to generate + /// + /// The preferred width in pixels + /// + /// The preferred height in pixels + /// + /// The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white) + /// + /// + ByteMatrix encode(System.String contents, BarcodeFormat format, int width, int height); + /// * + /// + /// The contents to encode in the barcode + /// + /// The barcode format to generate + /// + /// The preferred width in pixels + /// + /// The preferred height in pixels + /// + /// Additional parameters to supply to the encoder + /// + /// The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white) + /// + /// + ByteMatrix encode(System.String contents, BarcodeFormat format, int width, int height, System.Collections.Hashtable hints); + } +} \ No newline at end of file diff --git a/csharp/WriterException.cs b/csharp/WriterException.cs new file mode 100755 index 000000000..6148a1ad6 --- /dev/null +++ b/csharp/WriterException.cs @@ -0,0 +1,40 @@ +/* +* 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. +*/ +namespace com.google.zxing +{ + using System; + + /// A base class which covers the range of exceptions which may occur when encoding a barcode using + /// the Writer framework. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class WriterException : System.Exception + { + + public WriterException() + : base() + { + } + + public WriterException(System.String message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/csharp/common/BaseMonochromeBitmapSource.cs b/csharp/common/BaseMonochromeBitmapSource.cs new file mode 100755 index 000000000..9d2ad27c9 --- /dev/null +++ b/csharp/common/BaseMonochromeBitmapSource.cs @@ -0,0 +1,196 @@ +/* +* 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 BitArray = com.google.zxing.common.BitArray; +using com.google.zxing.common; +namespace com.google.zxing +{ + /** + * @author dswitkin@google.com (Daniel Switkin) + */ + public abstract class BaseMonochromeBitmapSource: MonochromeBitmapSource + { + private static int LUMINANCE_BITS = 5; + private static int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; + private static int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + + private int blackPoint; + private BlackPointEstimationMethod lastMethod; + private int lastArgument; + private int[] luminances; + + protected BaseMonochromeBitmapSource() { + blackPoint = 0x7F; + lastMethod = null; + lastArgument = 0; + } + + private void initLuminances() { + if (luminances == null) { + int width = getWidth(); + int height = getHeight(); + int max = width > height ? width : height; + luminances = new int[max]; + } + } + + public bool isBlack(int x, int y) { + return getLuminance(x, y) < blackPoint; + } + + public BitArray getBlackRow(int y, BitArray row, int startX, int getWidth) { + if (row == null || row.getSize() < getWidth) { + row = new BitArray(getWidth); + } else { + row.clear(); + } + + // Reuse the same int array each time + initLuminances(); + luminances = getLuminanceRow(y, luminances); + + // If the current decoder calculated the blackPoint based on one row, assume we're trying to + // decode a 1D barcode, and apply some sharpening. + if (lastMethod.Equals(BlackPointEstimationMethod.ROW_SAMPLING)) { + int left = luminances[startX]; + int center = luminances[startX + 1]; + for (int x = 1; x < getWidth - 1; x++) { + int right = luminances[startX + x + 1]; + // Simple -1 4 -1 box filter with a weight of 2 + int luminance = ((center << 2) - left - right) >> 1; + if (luminance < blackPoint) { + row.set(x); + } + left = center; + center = right; + } + } else { + for (int x = 0; x < getWidth; x++) { + if (luminances[startX + x] < blackPoint) { + row.set(x); + } + } + } + return row; + } + + public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight) { + if (column == null || column.getSize() < getHeight) { + column = new BitArray(getHeight); + } else { + column.clear(); + } + + // Reuse the same int array each time + initLuminances(); + luminances = getLuminanceColumn(x, luminances); + + // We don't handle "row sampling" specially here + for (int y = 0; y < getHeight; y++) { + if (luminances[startY + y] < blackPoint) { + column.set(y); + } + } + return column; + } + + public void estimateBlackPoint(BlackPointEstimationMethod method, int argument){ + if (!method.Equals(lastMethod) || argument != lastArgument) { + int width = getWidth(); + int height = getHeight(); + int[] histogram = new int[LUMINANCE_BUCKETS]; + if (method.Equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) { + int minDimension = width < height ? width : height; + int startX = (width - minDimension) >> 1; + int startY = (height - minDimension) >> 1; + for (int n = 0; n < minDimension; n++) { + int luminance = getLuminance(startX + n, startY + n); + histogram[luminance >> LUMINANCE_SHIFT]++; + } + } else if (method.Equals(BlackPointEstimationMethod.ROW_SAMPLING)) { + if (argument < 0 || argument >= height) { + throw new Exception("Row is not within the image: " + argument); + } + initLuminances(); + luminances = getLuminanceRow(argument, luminances); + for (int x = 0; x < width; x++) { + histogram[luminances[x] >> LUMINANCE_SHIFT]++; + } + } else { + throw new Exception("Unknown method: " + method); + } + blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT; + lastMethod = method; + lastArgument = argument; + } + } + + public BlackPointEstimationMethod getLastEstimationMethod() { + return lastMethod; + } + + public MonochromeBitmapSource rotateCounterClockwise() { + throw new Exception("Rotate not supported"); + } + + public bool isRotateSupported() { + return false; + } + + // These two methods should not need to exist because they are defined in the interface that + // this abstract class implements. However this seems to cause problems on some Nokias. + // So we write these redundant declarations. + + public abstract int getHeight(); + + public abstract int getWidth(); + + /** + * Retrieves the luminance at the pixel x,y in the bitmap. This method is only used for estimating + * the black point and implementing getBlackRow() - it is not meant for decoding, hence it is not + * part of MonochromeBitmapSource itself, and is protected. + * + * @param x The x coordinate in the image. + * @param y The y coordinate in the image. + * @return The luminance value between 0 and 255. + */ + protected abstract int getLuminance(int x, int y); + + /** + * This is the main mechanism for retrieving luminance data. It is dramatically more efficient + * than repeatedly calling getLuminance(). As above, this is not meant for decoders. + * + * @param y The row to fetch + * @param row The array to write luminance values into. It is strongly suggested that you + * allocate this yourself, making sure row.length >= getWidth(), and reuse the same + * array on subsequent calls for performance. If you pass null, you will be flogged, + * but then I will take pity on you and allocate a sufficient array internally. + * @return The array containing the luminance data. This is the same as row if it was usable. + */ + protected abstract int[] getLuminanceRow(int y, int[] row); + + /** + * The same as getLuminanceRow(), but for columns. + * + * @param x The column to fetch + * @param column The array to write luminance values into. See above. + * @return The array containing the luminance data. + */ + protected abstract int[] getLuminanceColumn(int x, int[] column); + + } +} + + diff --git a/csharp/common/BitArray.cs b/csharp/common/BitArray.cs new file mode 100755 index 000000000..84a8fe4f6 --- /dev/null +++ b/csharp/common/BitArray.cs @@ -0,0 +1,178 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class BitArray + { + // TODO: I have changed these members to be public so ProGuard can inline get() and set(). Ideally + // they'd be private and we'd use the -allowaccessmodification flag, but Dalvik rejects the + // resulting binary at runtime on Android. If we find a solution to this, these should be changed + // back to private. + public int[] bits; + public int Size; + + public BitArray(int size) { + if (size < 1) { + throw new Exception("size must be at least 1"); + } + this.Size = size; + this.bits = makeArray(size); + } + + public int getSize() { + return Size; + } + + /** + * @param i bit to get + * @return true iff bit i is set + */ + public bool get(int i) { + return (bits[i >> 5] & (1 << (i & 0x1F))) != 0; + } + + /** + * Sets bit i. + * + * @param i bit to set + */ + public void set(int i) { + bits[i >> 5] |= 1 << (i & 0x1F); + } + + /** + * Sets a block of 32 bits, starting at bit i. + * + * @param i first bit to set + * @param newBits the new value of the next 32 bits. Note again that the least-significant bit + * corresponds to bit i, the next-least-significant to i+1, and so on. + */ + public void setBulk(int i, int newBits) { + bits[i >> 5] = newBits; + } + + /** + * Clears all bits (sets to false). + */ + public void clear() { + int max = bits.Length; + for (int i = 0; i < max; i++) { + bits[i] = 0; + } + } + + /** + * Efficient method to check if a range of bits is set, or not set. + * + * @param start start of range, inclusive. + * @param end end of range, exclusive + * @param value if true, checks that bits in range are set, otherwise checks that they are not set + * @return true iff all bits are set or not set in range, according to value argument + * @throws IllegalArgumentException if end is less than or equal to start + */ + public bool isRange(int start, int end, bool value) { + if (end < start) { + throw new Exception(); + } + if (end == start) { + return true; // empty range matches + } + end--; // will be easier to treat this as the last actually set bit -- inclusive + int firstInt = start >> 5; + int lastInt = end >> 5; + for (int i = firstInt; i <= lastInt; i++) { + int firstBit = i > firstInt ? 0 : start & 0x1F; + int lastBit = i < lastInt ? 31 : end & 0x1F; + int mask; + if (firstBit == 0 && lastBit == 31) { + mask = -1; + } else { + mask = 0; + for (int j = firstBit; j <= lastBit; j++) { + mask |= 1 << j; + } + } + + // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is, + // equals the mask, or we're looking for 0s and the masked portion is not all 0s + if ((bits[i] & mask) != (value ? mask : 0)) { + return false; + } + } + return true; + } + + /** + * @return underlying array of ints. The first element holds the first 32 bits, and the least + * significant bit is bit 0. + */ + public int[] getBitArray() { + return bits; + } + + /** + * Reverses all bits in the array. + */ + public void reverse() { + int[] newBits = makeArray(Size); + int max = newBits.Length; + for (int i = 0; i < max; i++) { + newBits[i] = 0; + } + int size = this.Size; + for (int i = 0; i < size; i++) { + if (get(size - i - 1)) { + newBits[i >> 5] |= 1 << (i & 0x1F); + } + } + bits = newBits; + } + + private static int[] makeArray(int size) { + int arraySize = size >> 5; + if ((size & 0x1F) != 0) { + arraySize++; + } + return new int[arraySize]; + } + + public String toString() { + StringBuilder result = new StringBuilder(Size); + for (int i = 0; i < Size; i++) { + if (i % 8 == 0) { + result.Append(' '); + } + result.Append(get(i) ? 'X' : '.'); + } + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/csharp/common/BitMatrix.cs b/csharp/common/BitMatrix.cs new file mode 100755 index 000000000..3d9a99339 --- /dev/null +++ b/csharp/common/BitMatrix.cs @@ -0,0 +1,129 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class BitMatrix + { + private int dimension; + private int[] bits; + + public BitMatrix(int dimension) { + if (dimension < 1) { + throw new Exception("dimension must be at least 1"); + } + this.dimension = dimension; + int numBits = dimension * dimension; + int arraySize = numBits >> 5; // one int per 32 bits + if ((numBits & 0x1F) != 0) { // plus one more if there are leftovers + arraySize++; + } + bits = new int[arraySize]; + } + + /** + * @param i row offset + * @param j column offset + * @return value of given bit in matrix + */ + public bool get(int i, int j) { + int offset = i + dimension * j; + //return ((bits[offset >> 5] >>> (offset & 0x1F)) & 0x01) != 0; + return ((SupportClass.URShift(bits[offset >> 5], (offset & 0x1F))) & 0x01) != 0; + } + + /** + *

Sets the given bit to true.

+ * + * @param i row offset + * @param j column offset + */ + public void set(int i, int j) { + int offset = i + dimension * j; + bits[offset >> 5] |= 1 << (offset & 0x1F); + } + + /** + *

Sets a square region of the bit matrix to true.

+ * + * @param topI row offset of region's top-left corner (inclusive) + * @param leftJ column offset of region's top-left corner (inclusive) + * @param height height of region + * @param width width of region + */ + public void setRegion(int topI, int leftJ, int height, int width) { + if (topI < 0 || leftJ < 0) { + throw new Exception("topI and leftJ must be nonnegative"); + } + if (height < 1 || width < 1) { + throw new Exception("height and width must be at least 1"); + } + int maxJ = leftJ + width; + int maxI = topI + height; + if (maxI > dimension || maxJ > dimension) { + throw new Exception( + "topI + height and leftJ + width must be <= matrix dimension"); + } + for (int j = leftJ; j < maxJ; j++) { + int jOffset = dimension * j; + for (int i = topI; i < maxI; i++) { + int offset = i + jOffset; + bits[offset >> 5] |= 1 << (offset & 0x1F); + } + } + } + + /** + * @return row/column dimension of this matrix + */ + public int getDimension() { + return dimension; + } + + /** + * @return array of ints holding internal representation of this matrix's bits + */ + public int[] getBits() { + return bits; + } + + public String toString() { + StringBuilder result = new StringBuilder(dimension * (dimension + 1)); + for (int i = 0; i < dimension; i++) { + for (int j = 0; j < dimension; j++) { + result.Append(get(i, j) ? "X " : " "); + } + result.Append('\n'); + } + return result.ToString(); + } + + } +} \ No newline at end of file diff --git a/csharp/common/BitSource.cs b/csharp/common/BitSource.cs new file mode 100755 index 000000000..91df66ee6 --- /dev/null +++ b/csharp/common/BitSource.cs @@ -0,0 +1,102 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class BitSource + { + private sbyte[] bytes; + private int byteOffset; + private int bitOffset; + + /** + * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. + * Bits are read within a byte from most-significant to least-significant bit. + */ + public BitSource(sbyte[] bytes) { + this.bytes = bytes; + } + + /** + * @param numBits number of bits to read + * @return int representing the bits read. The bits will appear as the least-significant + * bits of the int + * @throws IllegalArgumentException if numBits isn't in [1,32] + */ + public int readBits(int numBits) { + if (numBits < 1 || numBits > 32) { + throw new Exception(); + } + + int result = 0; + + // First, read remainder from current byte + if (bitOffset > 0) { + int bitsLeft = 8 - bitOffset; + int toRead = numBits < bitsLeft ? numBits : bitsLeft; + int bitsToNotRead = bitsLeft - toRead; + int mask = (0xFF >> (8 - toRead)) << bitsToNotRead; + result = (bytes[byteOffset] & mask) >> bitsToNotRead; + numBits -= toRead; + bitOffset += toRead; + if (bitOffset == 8) { + bitOffset = 0; + byteOffset++; + } + } + + // Next read whole bytes + if (numBits > 0) { + while (numBits >= 8) { + result = (result << 8) | (bytes[byteOffset] & 0xFF); + byteOffset++; + numBits -= 8; + } + + // Finally read a partial byte + if (numBits > 0) { + int bitsToNotRead = 8 - numBits; + int mask = (0xFF >> bitsToNotRead) << bitsToNotRead; + result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead); + bitOffset += numBits; + } + } + + return result; + } + + /** + * @return number of bits that can be read successfully + */ + public int available() { + return 8 * (bytes.Length - byteOffset) - bitOffset; + } + } +} \ No newline at end of file diff --git a/csharp/common/BlackPointEstimator.cs b/csharp/common/BlackPointEstimator.cs new file mode 100755 index 000000000..d922193bb --- /dev/null +++ b/csharp/common/BlackPointEstimator.cs @@ -0,0 +1,118 @@ +/* +* 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; +namespace com.google.zxing.common +{ + + ///

Encapsulates logic that estimates the optimal "black point", the luminance value + /// which is the best line between "white" and "black" in a grayscale image.

+ /// + ///

For an interesting discussion of this issue, see + /// http://webdiis.unizar.es/~neira/12082/thresholding.pdf. + ///

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + /// dswitkin@google.com (Daniel Switkin) + /// + public sealed class BlackPointEstimator + { + private BlackPointEstimator() + { + } + + /** + *

Given an array of counts of luminance values (i.e. a histogram), this method + * decides which bucket of values corresponds to the black point -- which bucket contains the + * count of the brightest luminance values that should be considered "black".

+ * + * @param histogram an array of counts of luminance values + * @return index within argument of bucket corresponding to brightest values which should be + * considered "black" + * @throws ReaderException if "black" and "white" appear to be very close in luminance in the image + */ + public static int estimate(int[] histogram) + { + try{ + + int numBuckets = histogram.Length; + int maxBucketCount = 0; + // Find tallest peak in histogram + int firstPeak = 0; + int firstPeakSize = 0; + for (int i = 0; i < numBuckets; i++) { + if (histogram[i] > firstPeakSize) { + firstPeak = i; + firstPeakSize = histogram[i]; + } + if (histogram[i] > maxBucketCount) { + maxBucketCount = histogram[i]; + } + } + + // Find second-tallest peak -- well, another peak that is tall and not + // so close to the first one + int secondPeak = 0; + int secondPeakScore = 0; + for (int i = 0; i < numBuckets; i++) { + int distanceToBiggest = i - firstPeak; + // Encourage more distant second peaks by multiplying by square of distance + int score = histogram[i] * distanceToBiggest * distanceToBiggest; + if (score > secondPeakScore) { + secondPeak = i; + secondPeakScore = score; + } + } + + // Put firstPeak first + if (firstPeak > secondPeak) { + int temp = firstPeak; + firstPeak = secondPeak; + secondPeak = temp; + } + + // Kind of aribtrary; if the two peaks are very close, then we figure there is so little + // dynamic range in the image, that discriminating black and white is too error-prone. + // Decoding the image/line is either pointless, or may in some cases lead to a false positive + // for 1D formats, which are relatively lenient. + // We arbitrarily say "close" is "<= 1/16 of the total histogram buckets apart" + if (secondPeak - firstPeak <= numBuckets >> 4) { + throw new ReaderException(""); + } + + // Find a valley between them that is low and closer to the white peak + int bestValley = secondPeak - 1; + int bestValleyScore = -1; + for (int i = secondPeak - 1; i > firstPeak; i--) { + int fromFirst = i - firstPeak; + // Favor a "valley" that is not too close to either peak -- especially not the black peak -- + // and that has a low value of course + int score = fromFirst * fromFirst * (secondPeak - i) * (maxBucketCount - histogram[i]); + if (score > bestValleyScore) { + bestValley = i; + bestValleyScore = score; + } + } + + return bestValley; + } + catch (Exception e) + { + throw (ReaderException) e; + } + } + } +} \ No newline at end of file diff --git a/csharp/common/ByteArray.cs b/csharp/common/ByteArray.cs new file mode 100755 index 000000000..5c4d828e2 --- /dev/null +++ b/csharp/common/ByteArray.cs @@ -0,0 +1,117 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class ByteArray + { + private static int INITIAL_SIZE = 32; + private sbyte[] bytes; + private int Size; + + public ByteArray() + { + bytes = null; + this.Size = 0; + } + + public ByteArray(int size) + { + bytes = new sbyte[size]; + this.Size = size; + } + + public ByteArray(sbyte[] byteArray) + { + bytes = byteArray; + this.Size = bytes.Length; + } + + /** + * Access an unsigned byte at location index. + * @param index The index in the array to access. + * @return The unsigned value of the byte as an int. + */ + public int at(int index) + { + return bytes[index] & 0xff; + } + + public void set(int index, int value) + { + bytes[index] = (sbyte)value; + } + + public int size() + { + return Size; + } + + public bool empty() + { + return Size == 0; + } + + public void appendByte(int value) + { + if (Size == 0 || Size >= bytes.Length) + { + int newSize = Math.Max(INITIAL_SIZE, Size << 1); + reserve(newSize); + } + bytes[Size] = (sbyte)value; + Size++; + } + + public void reserve(int capacity) + { + if (bytes == null || bytes.Length < capacity) + { + sbyte[] newArray = new sbyte[capacity]; + if (bytes != null) + { + System.Array.Copy(bytes, 0, newArray, 0, bytes.Length); + } + bytes = newArray; + } + } + + // Copy count bytes from array source starting at offset. + public void set(sbyte[] source, int offset, int count) + { + bytes = new sbyte[count]; + Size = count; + for (int x = 0; x < count; x++) + { + bytes[x] = source[offset + x]; + } + } + } +} \ No newline at end of file diff --git a/csharp/common/ByteMatrix.cs b/csharp/common/ByteMatrix.cs new file mode 100755 index 000000000..b1daa7210 --- /dev/null +++ b/csharp/common/ByteMatrix.cs @@ -0,0 +1,115 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class ByteMatrix + { + private sbyte[][] bytes; + private int Height; + private int Width; + + + public ByteMatrix(int height, int width) { + bytes = new sbyte[height][]; + for (int i = 0; i < height; i++) { + bytes[i] = new sbyte[width]; + } + this.Height = height; + this.Width = width; + } + + public int height() + { + return Height; + } + + public int width() + { + return Width; + } + + public sbyte get(int y, int x) + { + return bytes[y][x]; + } + + public sbyte[][] getArray() + { + return bytes; + } + + public void set(int y, int x, sbyte value) + { + bytes[y][x] = value; + } + + public void set(int y, int x, int value) + { + bytes[y][x] = (sbyte)value; + } + + public void clear(sbyte value) + { + for (int y = 0; y < Height; ++y) + { + for (int x = 0; x < Width; ++x) + { + bytes[y][x] = value; + } + } + } + + public String toString() + { + StringBuilder result = new StringBuilder(); + for (int y = 0; y < Height; ++y) + { + for (int x = 0; x < Width; ++x) + { + switch (bytes[y][x]) + { + case 0: + result.Append(" 0"); + break; + case 1: + result.Append(" 1"); + break; + default: + result.Append(" "); + break; + } + } + result.Append('\n'); + } + return result.ToString(); + } + } +} \ No newline at end of file diff --git a/csharp/common/CharacterSetECI.cs b/csharp/common/CharacterSetECI.cs new file mode 100755 index 000000000..35e0e109e --- /dev/null +++ b/csharp/common/CharacterSetECI.cs @@ -0,0 +1,101 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + using System.Collections; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class CharacterSetECI : ECI + { + private static Hashtable VALUE_TO_ECI=new Hashtable(29); + /*static VALUE_TO_ECI = new Hashtable(29){ + // TODO figure out if these values are even right! + addCharacterSet(0, "Cp437"); + addCharacterSet(1, "ISO8859_1"); + addCharacterSet(2, "Cp437"); + addCharacterSet(3, "ISO8859_1"); + addCharacterSet(4, "ISO8859_2"); + addCharacterSet(5, "ISO8859_3"); + addCharacterSet(6, "ISO8859_4"); + addCharacterSet(7, "ISO8859_5"); + addCharacterSet(8, "ISO8859_6"); + addCharacterSet(9, "ISO8859_7"); + addCharacterSet(10, "ISO8859_8"); + addCharacterSet(11, "ISO8859_9"); + addCharacterSet(12, "ISO8859_10"); + addCharacterSet(13, "ISO8859_11"); + addCharacterSet(15, "ISO8859_13"); + addCharacterSet(16, "ISO8859_14"); + addCharacterSet(17, "ISO8859_15"); + addCharacterSet(18, "ISO8859_16"); + addCharacterSet(20, "SJIS"); + }*/ + + private String encodingName; + + private CharacterSetECI(int value, String encodingName):base(value) { + addCharacterSet(0, "Cp437"); + addCharacterSet(1, "ISO8859_1"); + addCharacterSet(2, "Cp437"); + addCharacterSet(3, "ISO8859_1"); + addCharacterSet(4, "ISO8859_2"); + addCharacterSet(5, "ISO8859_3"); + addCharacterSet(6, "ISO8859_4"); + addCharacterSet(7, "ISO8859_5"); + addCharacterSet(8, "ISO8859_6"); + addCharacterSet(9, "ISO8859_7"); + addCharacterSet(10, "ISO8859_8"); + addCharacterSet(11, "ISO8859_9"); + addCharacterSet(12, "ISO8859_10"); + addCharacterSet(13, "ISO8859_11"); + addCharacterSet(15, "ISO8859_13"); + addCharacterSet(16, "ISO8859_14"); + addCharacterSet(17, "ISO8859_15"); + addCharacterSet(18, "ISO8859_16"); + addCharacterSet(20, "SJIS"); + this.encodingName = encodingName; + } + + public String getEncodingName() { + return encodingName; + } + + private static void addCharacterSet(int value, String encodingName) { + VALUE_TO_ECI.Add(value, new CharacterSetECI(value, encodingName)); + } + + public static CharacterSetECI getCharacterSetECIByValue(int value) { + CharacterSetECI eci = (CharacterSetECI) VALUE_TO_ECI[value]; + if (eci == null) { + throw new Exception("Unsupported value: " + value); + } + return eci; + } + } +} \ No newline at end of file diff --git a/csharp/common/Collections.cs b/csharp/common/Collections.cs new file mode 100755 index 000000000..85e8c74a6 --- /dev/null +++ b/csharp/common/Collections.cs @@ -0,0 +1,64 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class Collections + { + + private Collections() + { + } + + /** + * Sorts its argument (destructively) using insert sort; in the context of this package + * insertion sort is simple and efficient given its relatively small inputs. + * + * @param vector vector to sort + * @param comparator comparator to define sort ordering + */ + public static void insertionSort(System.Collections.ArrayList vector, Comparator comparator) + { + int max = vector.Count; + for (int i = 1; i < max; i++) + { + System.Object value_Renamed = vector[i]; + int j = i - 1; + System.Object valueB; + while (j >= 0 && comparator.compare((valueB = vector[j]), value_Renamed) > 0) + { + vector[j + 1] = valueB; + j--; + } + vector[j + 1] = value_Renamed; + } + } + } +} \ No newline at end of file diff --git a/csharp/common/Comparator.cs b/csharp/common/Comparator.cs new file mode 100755 index 000000000..4d8bc9f98 --- /dev/null +++ b/csharp/common/Comparator.cs @@ -0,0 +1,27 @@ +/* +* 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; +namespace com.google.zxing.common +{ + + /// This is merely a clone of Comparator since it is not available in + /// CLDC 1.1 / MIDP 2.0. + /// + public interface Comparator + { + + int compare(System.Object o1, System.Object o2); + } +} \ No newline at end of file diff --git a/csharp/common/DecoderResult.cs b/csharp/common/DecoderResult.cs new file mode 100755 index 000000000..ad3c81a66 --- /dev/null +++ b/csharp/common/DecoderResult.cs @@ -0,0 +1,63 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class DecoderResult + { + private sbyte[] rawBytes; + private String text; + private System.Collections.ArrayList byteSegments; + + public DecoderResult(sbyte[] rawBytes, String text, System.Collections.ArrayList byteSegments) + { + if (rawBytes == null && text == null) { + throw new Exception(); + } + this.rawBytes = rawBytes; + this.text = text; + this.byteSegments = byteSegments; + } + + public sbyte[] getRawBytes() { + return this.rawBytes; + } + + public String getText() { + return text; + } + + public System.Collections.ArrayList getByteSegments() + { + return byteSegments; + } + + } +} \ No newline at end of file diff --git a/csharp/common/DefaultGridSampler.cs b/csharp/common/DefaultGridSampler.cs new file mode 100755 index 000000000..95d1ee0a6 --- /dev/null +++ b/csharp/common/DefaultGridSampler.cs @@ -0,0 +1,73 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using MonochromeBitmapSource = com.google.zxing.MonochromeBitmapSource; + using ReaderException = com.google.zxing.ReaderException; + /// Sean Owen + /// + /// + public sealed class DefaultGridSampler : GridSampler + { + + public override BitMatrix sampleGrid(MonochromeBitmapSource image, int dimension, float p1ToX, float p1ToY, float p2ToX, float p2ToY, float p3ToX, float p3ToY, float p4ToX, float p4ToY, float p1FromX, float p1FromY, float p2FromX, float p2FromY, float p3FromX, float p3FromY, float p4FromX, float p4FromY) + { + PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY); + + BitMatrix bits = new BitMatrix(dimension); + float[] points = new float[dimension << 1]; + for (int i = 0; i < dimension; i++) + { + int max = points.Length; + float iValue = (float)i + 0.5f; + for (int j = 0; j < max; j += 2) + { + points[j] = (float)(j >> 1) + 0.5f; + points[j + 1] = iValue; + } + transform.transformPoints(points); + // Quick check to see if points transformed to something inside the image; + // sufficent to check the endpoints + checkAndNudgePoints(image, points); + try + { + for (int j = 0; j < max; j += 2) + { + //UPGRADE_WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"' + if (image.isBlack((int)points[j], (int)points[j + 1])) + { + // Black(-ish) pixel + bits.set(i, j >> 1); + } + } + } + catch (System.IndexOutOfRangeException aioobe) + { + // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting + // transform gets "twisted" such that it maps a straight line of points to a set of points + // whose endpoints are in bounds, but others are not. There is probably some mathematical + // way to detect this about the transformation that I don't know yet. + // This results in an ugly runtime exception despite our clever checks above -- can't have that. + // We could check each point's coordinates but that feels duplicative. We settle for + // catching and wrapping ArrayIndexOutOfBoundsException. + throw new ReaderException(aioobe.Message); + } + } + return bits; + } + } +} \ No newline at end of file diff --git a/csharp/common/DetectorResult.cs b/csharp/common/DetectorResult.cs new file mode 100755 index 000000000..bce786a24 --- /dev/null +++ b/csharp/common/DetectorResult.cs @@ -0,0 +1,51 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class DetectorResult + { + private BitMatrix bits; + private ResultPoint[] points; + + public DetectorResult(BitMatrix bits, ResultPoint[] points) { + this.bits = bits; + this.points = points; + } + + public BitMatrix getBits() { + return bits; + } + + public ResultPoint[] getPoints() { + return points; + } + } +} diff --git a/csharp/common/ECI.cs b/csharp/common/ECI.cs new file mode 100755 index 000000000..05f8dc672 --- /dev/null +++ b/csharp/common/ECI.cs @@ -0,0 +1,56 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public abstract class ECI + { + private int value; + + public ECI(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static ECI getECIByValue(int value) { + if (value < 0 || value > 999999) { + throw new Exception("Bad ECI value: " + value); + } + if (value < 900) { // Character set ECIs use 000000 - 000899 + return CharacterSetECI.getCharacterSetECIByValue(value); + } + throw new Exception("Unsupported ECI value: " + value); + } + + } +} \ No newline at end of file diff --git a/csharp/common/GenericResultPoint.cs b/csharp/common/GenericResultPoint.cs new file mode 100755 index 000000000..3a3c20a1e --- /dev/null +++ b/csharp/common/GenericResultPoint.cs @@ -0,0 +1,136 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using System.Text; + + /// A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a + /// unsigned container, it's up to you to do byteValue & 0xff at each location. + /// * + /// JAVAPORT: I'm not happy about the argument ordering throughout the file, as I always like to have + /// the horizontal component first, but this is for compatibility with the C++ code. The original + /// code was a 2D array of ints, but since it only ever gets assigned -1, 0, and 1, I'm going to use + /// less memory and go with bytes. + /// * + /// + /// dswitkin@google.com (Daniel Switkin) + /// + /// + public sealed class GenericResultPoint : ResultPoint + { + private float posX; + private float posY; + + public GenericResultPoint(float posX, float posY) { + this.posX = posX; + this.posY = posY; + } + + public float getX() { + return posX; + } + + public float getY() { + return posY; + } + + public String toString() { + StringBuilder result = new StringBuilder(25); + result.Append('('); + result.Append(posX); + result.Append(','); + result.Append(posY); + result.Append(')'); + return result.ToString(); + } + + public bool equals(Object other) { + + if (other.GetType() == typeof(GenericResultPoint)) { + GenericResultPoint otherPoint = (GenericResultPoint) other; + return posX == otherPoint.posX && posY == otherPoint.posY; + } + return false; + } + + public int hashCode() { + return 31 * posX.GetHashCode() + posY.GetHashCode(); + } + + /** + *

Orders an array of three ResultPoints in an order [A,B,C] such that AB < AC and + * BC < AC and the angle between BC and BA is less than 180 degrees. + */ + public static void orderBestPatterns(ResultPoint[] patterns) { + + // Find distances between pattern centers + float zeroOneDistance = distance(patterns[0], patterns[1]); + float oneTwoDistance = distance(patterns[1], patterns[2]); + float zeroTwoDistance = distance(patterns[0], patterns[2]); + + ResultPoint pointA, pointB, pointC; + // Assume one closest to other two is B; A and C will just be guesses at first + if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) { + pointB = patterns[0]; + pointA = patterns[1]; + pointC = patterns[2]; + } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) { + pointB = patterns[1]; + pointA = patterns[0]; + pointC = patterns[2]; + } else { + pointB = patterns[2]; + pointA = patterns[0]; + pointC = patterns[1]; + } + + // Use cross product to figure out whether A and C are correct or flipped. + // This asks whether BC x BA has a positive z component, which is the arrangement + // we want for A, B, C. If it's negative, then we've got it flipped around and + // should swap A and C. + if (crossProductZ(pointA, pointB, pointC) < 0.0f) { + ResultPoint temp = pointA; + pointA = pointC; + pointC = temp; + } + + patterns[0] = pointA; + patterns[1] = pointB; + patterns[2] = pointC; + } + + + /** + * @return distance between two points + */ + public static float distance(ResultPoint pattern1, ResultPoint pattern2) { + float xDiff = pattern1.getX() - pattern2.getX(); + float yDiff = pattern1.getY() - pattern2.getY(); + return (float) Math.Sqrt((double) (xDiff * xDiff + yDiff * yDiff)); + } + + /** + * Returns the z component of the cross product between vectors BC and BA. + */ + public static float crossProductZ(ResultPoint pointA, ResultPoint pointB, ResultPoint pointC) { + float bX = pointB.getX(); + float bY = pointB.getY(); + return ((pointC.getX() - bX) * (pointA.getY() - bY)) - ((pointC.getY() - bY) * (pointA.getX() - bX)); + } + + } +} \ No newline at end of file diff --git a/csharp/common/GridSampler.cs b/csharp/common/GridSampler.cs new file mode 100755 index 000000000..1964708d9 --- /dev/null +++ b/csharp/common/GridSampler.cs @@ -0,0 +1,168 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + using MonochromeBitmapSource = com.google.zxing.MonochromeBitmapSource; + using ReaderException = com.google.zxing.ReaderException; + ///

Implementations of this class can, given locations of finder patterns for a QR code in an + /// image, sample the right points in the image to reconstruct the QR code, accounting for + /// perspective distortion. It is abstracted since it is relatively expensive and should be allowed + /// to take advantage of platform-specific optimized implementations, like Sun's Java Advanced + /// Imaging library, but which may not be available in other environments such as J2ME, and vice + /// versa. + /// * + /// The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)} + /// with an instance of a class which implements this interface. + /// * + /// + /// Sean Owen + /// + /// + public abstract class GridSampler + { + public static GridSampler Instance + { + get + { + // No real point in trying to make this thread-safe; + // doesn't matter if a second instance is created + if (gridSampler == null) + { + gridSampler = new DefaultGridSampler(); + } + return gridSampler; + } + + } + + private static GridSampler gridSampler = null; + ///

Samples an image for a square matrix of bits of the given dimension. This is used to extract the + /// black/white modules of a 2D barcode like a QR Code found in an image. Because this barcode may be + /// rotated or perspective-distorted, the caller supplies four points in the source image that define + /// known points in the barcode, so that the image may be sampled appropriately.

+ /// * + ///

The last eight "from" parameters are four X/Y coordinate pairs of locations of points in + /// the image that define some significant points in the image to be sample. For example, + /// these may be the location of finder pattern in a QR Code.

+ /// * + ///

The first eight "to" parameters are four X/Y coordinate pairs measured in the destination + /// {@link BitMatrix}, from the top left, where the known points in the image given by the "from" parameters + /// map to.

+ /// * + ///

These 16 parameters define the transformation needed to sample the image.

+ /// * + ///
+ /// image to sample + /// + /// width/height of {@link BitMatrix} to sample from iamge + /// + /// {@link BitMatrix} representing a grid of points sampled from the image within a region + /// defined by the "from" parameters + /// @throws ReaderException if image can't be sampled, for example, if the transformation defined by + /// the given points is invalid or results in sampling outside the image boundaries + /// + /// + public abstract BitMatrix sampleGrid(MonochromeBitmapSource image, int dimension, float p1ToX, float p1ToY, float p2ToX, float p2ToY, float p3ToX, float p3ToY, float p4ToX, float p4ToY, float p1FromX, float p1FromY, float p2FromX, float p2FromY, float p3FromX, float p3FromY, float p4FromX, float p4FromY); + + ///

Checks a set of points that have been transformed to sample points on an image against + /// the image's dimensions to see if the point are even within the image.

+ /// * + ///

This method will actually "nudge" the endpoints back onto the image if they are found to be barely + /// (less than 1 pixel) off the image. This accounts for imperfect detection of finder patterns in an image + /// where the QR Code runs all the way to the image border.

+ /// * + ///

For efficiency, the method will check points from either end of the line until one is found + /// to be within the image. Because the set of points are assumed to be linear, this is valid.

+ /// * + ///
+ /// image into which the points should map + /// + /// actual points in x1,y1,...,xn,yn form + /// @throws ReaderException if an endpoint is lies outside the image boundaries + /// + /// + protected internal static void checkAndNudgePoints(MonochromeBitmapSource image, float[] points) + { + int width = image.getWidth(); + int height = image.getHeight(); + // Check and nudge points from start until we see some that are OK: + bool nudged = true; + for (int offset = 0; offset < points.Length && nudged; offset += 2) + { + int x = (int)points[offset]; + int y = (int)points[offset + 1]; + if (x < -1 || x > width || y < -1 || y > height) + { + throw new ReaderException(""); + } + nudged = false; + if (x == -1) + { + points[offset] = 0.0f; + nudged = true; + } + else if (x == width) + { + points[offset] = width - 1; + nudged = true; + } + if (y == -1) + { + points[offset + 1] = 0.0f; + nudged = true; + } + else if (y == height) + { + points[offset + 1] = height - 1; + nudged = true; + } + } + // Check and nudge points from end: + nudged = true; + for (int offset = points.Length - 2; offset >= 0 && nudged; offset -= 2) + { + int x = (int)points[offset]; + int y = (int)points[offset + 1]; + if (x < -1 || x > width || y < -1 || y > height) + { + throw new ReaderException(""); + } + nudged = false; + if (x == -1) + { + points[offset] = 0.0f; + nudged = true; + } + else if (x == width) + { + points[offset] = width - 1; + nudged = true; + } + if (y == -1) + { + points[offset + 1] = 0.0f; + nudged = true; + } + else if (y == height) + { + points[offset + 1] = height - 1; + nudged = true; + } + } + } + } +} \ No newline at end of file diff --git a/csharp/common/PerspectiveTransform.cs b/csharp/common/PerspectiveTransform.cs new file mode 100755 index 000000000..a42a83e7e --- /dev/null +++ b/csharp/common/PerspectiveTransform.cs @@ -0,0 +1,131 @@ +/* +* 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. +*/ +namespace com.google.zxing.common +{ + using System; + + ///

This class implements a perspective transform in two dimensions. Given four source and four destination + /// points, it will compute the transformation implied between them. The code is based directly upon section + /// 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.

+ /// * + ///
+ /// Sean Owen + /// + /// + sealed class PerspectiveTransform + { + + //UPGRADE_NOTE: Final was removed from the declaration of 'a11 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a12 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a13 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a21 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a22 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a23 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a31 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + //UPGRADE_NOTE: Final was removed from the declaration of 'a33 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' + private float a11; + private float a12; + private float a13; + private float a21; + private float a22; + private float a23; + private float a31; + private float a32; + private float a33; + + private PerspectiveTransform(float a11, float a21, float a31, float a12, float a22, float a32, float a13, float a23, float a33) + { + this.a11 = a11; + this.a12 = a12; + this.a13 = a13; + this.a21 = a21; + this.a22 = a22; + this.a23 = a23; + this.a31 = a31; + this.a32 = a32; + this.a33 = a33; + } + + internal static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float x0p, float y0p, float x1p, float y1p, float x2p, float y2p, float x3p, float y3p) + { + + PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3); + PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p); + return sToQ.times(qToS); + } + + internal void transformPoints(float[] points) + { + int max = points.Length; + float a11 = this.a11; + float a12 = this.a12; + float a13 = this.a13; + float a21 = this.a21; + float a22 = this.a22; + float a23 = this.a23; + float a31 = this.a31; + float a32 = this.a32; + float a33 = this.a33; + for (int i = 0; i < max; i += 2) + { + float x = points[i]; + float y = points[i + 1]; + float denominator = a13 * x + a23 * y + a33; + points[i] = (a11 * x + a21 * y + a31) / denominator; + points[i + 1] = (a12 * x + a22 * y + a32) / denominator; + } + } + + internal static PerspectiveTransform squareToQuadrilateral(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) + { + float dy2 = y3 - y2; + float dy3 = y0 - y1 + y2 - y3; + if (dy2 == 0.0f && dy3 == 0.0f) + { + return new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0.0f, 0.0f, 1.0f); + } + else + { + float dx1 = x1 - x2; + float dx2 = x3 - x2; + float dx3 = x0 - x1 + x2 - x3; + float dy1 = y1 - y2; + float denominator = dx1 * dy2 - dx2 * dy1; + float a13 = (dx3 * dy2 - dx2 * dy3) / denominator; + float a23 = (dx1 * dy3 - dx3 * dy1) / denominator; + return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1.0f); + } + } + + private static PerspectiveTransform quadrilateralToSquare(float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3) + { + // Here, the adjoint serves as the inverse: + return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint(); + } + + internal PerspectiveTransform buildAdjoint() + { + // Adjoint is the transpose of the cofactor matrix: + return new PerspectiveTransform(a22 * a33 - a23 * a32, a23 * a31 - a21 * a33, a21 * a32 - a22 * a31, a13 * a32 - a12 * a33, a11 * a33 - a13 * a31, a12 * a31 - a11 * a32, a12 * a23 - a13 * a22, a13 * a21 - a11 * a23, a11 * a22 - a12 * a21); + } + + internal PerspectiveTransform times(PerspectiveTransform other) + { + return new PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13, a11 * other.a21 + a21 * other.a22 + a31 * other.a23, a11 * other.a31 + a21 * other.a32 + a31 * other.a33, a12 * other.a11 + a22 * other.a12 + a32 * other.a13, a12 * other.a21 + a22 * other.a22 + a32 * other.a23, a12 * other.a31 + a22 * other.a32 + a32 * other.a33, a13 * other.a11 + a23 * other.a12 + a33 * other.a13, a13 * other.a21 + a23 * other.a22 + a33 * other.a23, a13 * other.a31 + a23 * other.a32 + a33 * other.a33); + } + } +} \ No newline at end of file diff --git a/csharp/common/reedsolomon/GF256.cs b/csharp/common/reedsolomon/GF256.cs new file mode 100755 index 000000000..f1aaeb3b0 --- /dev/null +++ b/csharp/common/reedsolomon/GF256.cs @@ -0,0 +1,151 @@ +/* +* 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; +namespace com.google.zxing.common.reedsolomon +{ + + ///

This class contains utility methods for performing mathematical operations over + /// the Galois Field GF(256). Operations use the primitive polynomial + /// x^8 + x^4 + x^3 + x^2 + 1 in calculations.

+ /// + ///

Throughout this package, elements of GF(256) are represented as an int + /// for convenience and speed (but at the cost of memory). + /// Only the bottom 8 bits are really used.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + public sealed class GF256 + { + public static GF256 QR_CODE_FIELD = new GF256(0x011D); // x^8 + x^4 + x^3 + x^2 + 1 + public static GF256 DATA_MATRIX_FIELD = new GF256(0x012D); // x^8 + x^5 + x^3 + x^2 + 1 + + private int[] expTable; + private int[] logTable; + private GF256Poly zero; + private GF256Poly one; + + /** + * Create a representation of GF(256) using the given primitive polynomial. + * + * @param primitive irreducible polynomial whose coefficients are represented by + * the bits of an int, where the least-significant bit represents the constant + * coefficient + */ + private GF256(int primitive) { + expTable = new int[256]; + logTable = new int[256]; + int x = 1; + for (int i = 0; i < 256; i++) { + expTable[i] = x; + x <<= 1; // x = x * 2; we're assuming the generator alpha is 2 + if (x >= 0x100) { + x ^= primitive; + } + } + for (int i = 0; i < 255; i++) { + logTable[expTable[i]] = i; + } + // logTable[0] == 0 but this should never be used + zero = new GF256Poly(this, new int[]{0}); + one = new GF256Poly(this, new int[]{1}); + } + + public GF256Poly getZero() { + return zero; + } + + public GF256Poly getOne() + { + return one; + } + + /** + * @return the monomial representing coefficient * x^degree + */ + public GF256Poly buildMonomial(int degree, int coefficient) + { + if (degree < 0) { + throw new ArgumentException(); + } + if (coefficient == 0) { + return zero; + } + int[] coefficients = new int[degree + 1]; + coefficients[0] = coefficient; + return new GF256Poly(this, coefficients); + } + + /** + * Implements both addition and subtraction -- they are the same in GF(256). + * + * @return sum/difference of a and b + */ + public static int addOrSubtract(int a, int b) { + return a ^ b; + } + + /** + * @return 2 to the power of a in GF(256) + */ + public int exp(int a) + { + return expTable[a]; + } + + /** + * @return base 2 log of a in GF(256) + */ + public int log(int a) + { + if (a == 0) { + throw new ArgumentException(); + } + return logTable[a]; + } + + /** + * @return multiplicative inverse of a + */ + public int inverse(int a) + { + if (a == 0) { + throw new ArithmeticException(); + } + return expTable[255 - logTable[a]]; + } + + /** + * @param a + * @param b + * @return product of a and b in GF(256) + */ + public int multiply(int a, int b) + { + if (a == 0 || b == 0) { + return 0; + } + if (a == 1) { + return b; + } + if (b == 1) { + return a; + } + return expTable[(logTable[a] + logTable[b]) % 255]; + } + + + } +} \ No newline at end of file diff --git a/csharp/common/reedsolomon/GF256Poly.cs b/csharp/common/reedsolomon/GF256Poly.cs new file mode 100755 index 000000000..86eb3d386 --- /dev/null +++ b/csharp/common/reedsolomon/GF256Poly.cs @@ -0,0 +1,274 @@ +/* +* 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.Text; +namespace com.google.zxing.common.reedsolomon +{ + + ///

Represents a polynomial whose coefficients are elements of GF(256). + /// Instances of this class are immutable.

+ /// + ///

Much credit is due to William Rucklidge since portions of this code are an indirect + /// port of his C++ Reed-Solomon implementation.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + public sealed class GF256Poly + { + private GF256 field; + private int[] coefficients; + + /** + * @param field the {@link GF256} instance representing the field to use + * to perform computations + * @param coefficients coefficients as ints representing elements of GF(256), arranged + * from most significant (highest-power term) coefficient to least significant + * @throws ArgumentException if argument is null or empty, + * or if leading coefficient is 0 and this is not a + * constant polynomial (that is, it is not the monomial "0") + */ + public GF256Poly(GF256 field, int[] coefficients) { + if (coefficients == null || coefficients.Length == 0) { + throw new ArgumentException(); + } + this.field = field; + int coefficientsLength = coefficients.Length; + if (coefficientsLength > 1 && coefficients[0] == 0) { + // Leading term must be non-zero for anything except the constant polynomial "0" + int firstNonZero = 1; + while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) { + firstNonZero++; + } + if (firstNonZero == coefficientsLength) { + this.coefficients = field.getZero().coefficients; + } else { + this.coefficients = new int[coefficientsLength - firstNonZero]; + System.Array.Copy(coefficients,firstNonZero,this.coefficients,0,this.coefficients.Length); + } + } else { + this.coefficients = coefficients; + } + } + + public int[] getCoefficients() + { + return coefficients; + } + + /** + * @return degree of this polynomial + */ + public int getDegree() + { + return coefficients.Length - 1; + } + + /** + * @return true iff this polynomial is the monomial "0" + */ + public bool isZero() + { + return coefficients[0] == 0; + } + + /** + * @return coefficient of x^degree term in this polynomial + */ + public int getCoefficient(int degree) + { + return coefficients[coefficients.Length - 1 - degree]; + } + + /** + * @return evaluation of this polynomial at a given point + */ + public int evaluateAt(int a) + { + if (a == 0) { + // Just return the x^0 coefficient + return getCoefficient(0); + } + int size = coefficients.Length; + int result = 0; + + if (a == 1) { + // Just the sum of the coefficients + result = 0; + for (int i = 0; i < size; i++) { + result = GF256.addOrSubtract(result, coefficients[i]); + } + return result; + } + + result = coefficients[0]; + for (int i = 1; i < size; i++) { + result = GF256.addOrSubtract(field.multiply(a, result), coefficients[i]); + } + return result; + } + + public GF256Poly addOrSubtract(GF256Poly other) + { + if (!field.Equals(other.field)) { + throw new ArgumentException("GF256Polys do not have same GF256 field"); + } + if (isZero()) { + return other; + } + if (other.isZero()) { + return this; + } + + int[] smallerCoefficients = this.coefficients; + int[] largerCoefficients = other.coefficients; + if (smallerCoefficients.Length > largerCoefficients.Length) { + int[] temp = smallerCoefficients; + smallerCoefficients = largerCoefficients; + largerCoefficients = temp; + } + int[] sumDiff = new int[largerCoefficients.Length]; + int lengthDiff = largerCoefficients.Length - smallerCoefficients.Length; + // Copy high-order terms only found in higher-degree polynomial's coefficients + System.Array.Copy(largerCoefficients, 0, sumDiff, 0, lengthDiff); + + for (int i = lengthDiff; i < largerCoefficients.Length; i++) { + sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]); + } + + return new GF256Poly(field, sumDiff); + } + + public GF256Poly multiply(GF256Poly other) + { + if (!field.Equals(other.field)) { + throw new ArgumentException("GF256Polys do not have same GF256 field"); + } + if (isZero() || other.isZero()) { + return field.getZero(); + } + int[] aCoefficients = this.coefficients; + int aLength = aCoefficients.Length; + int[] bCoefficients = other.coefficients; + int bLength = bCoefficients.Length; + int[] product = new int[aLength + bLength - 1]; + for (int i = 0; i < aLength; i++) { + int aCoeff = aCoefficients[i]; + for (int j = 0; j < bLength; j++) { + product[i + j] = GF256.addOrSubtract(product[i + j], + field.multiply(aCoeff, bCoefficients[j])); + } + } + return new GF256Poly(field, product); + } + + public GF256Poly multiply(int scalar) + { + if (scalar == 0) { + return field.getZero(); + } + if (scalar == 1) { + return this; + } + int size = coefficients.Length; + int[] product = new int[size]; + for (int i = 0; i < size; i++) { + product[i] = field.multiply(coefficients[i], scalar); + } + return new GF256Poly(field, product); + } + + public GF256Poly multiplyByMonomial(int degree, int coefficient) + { + if (degree < 0) { + throw new ArgumentException(); + } + if (coefficient == 0) { + return field.getZero(); + } + int size = coefficients.Length; + int[] product = new int[size + degree]; + for (int i = 0; i < size; i++) { + product[i] = field.multiply(coefficients[i], coefficient); + } + return new GF256Poly(field, product); + } + + public GF256Poly[] divide(GF256Poly other) + { + if (!field.Equals(other.field)) { + throw new ArgumentException("GF256Polys do not have same GF256 field"); + } + if (other.isZero()) { + throw new ArgumentException("Divide by 0"); + } + + GF256Poly quotient = field.getZero(); + GF256Poly remainder = this; + + int denominatorLeadingTerm = other.getCoefficient(other.getDegree()); + int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm); + + while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) { + int degreeDifference = remainder.getDegree() - other.getDegree(); + int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm); + GF256Poly term = other.multiplyByMonomial(degreeDifference, scale); + GF256Poly iterationQuotient = field.buildMonomial(degreeDifference, scale); + quotient = quotient.addOrSubtract(iterationQuotient); + remainder = remainder.addOrSubtract(term); + } + + return new GF256Poly[] { quotient, remainder }; + } + + public String toString() { + StringBuilder result = new StringBuilder(8 * getDegree()); + for (int degree = getDegree(); degree >= 0; degree--) { + int coefficient = getCoefficient(degree); + if (coefficient != 0) { + if (coefficient < 0) { + result.Append(" - "); + coefficient = -coefficient; + } else { + if (result.Length > 0) { + result.Append(" + "); + } + } + if (degree == 0 || coefficient != 1) { + int alphaPower = field.log(coefficient); + if (alphaPower == 0) { + result.Append('1'); + } else if (alphaPower == 1) { + result.Append('a'); + } else { + result.Append("a^"); + result.Append(alphaPower); + } + } + if (degree != 0) { + if (degree == 1) { + result.Append('x'); + } else { + result.Append("x^"); + result.Append(degree); + } + } + } + } + return result.ToString(); + } + + } +} \ No newline at end of file diff --git a/csharp/common/reedsolomon/ReedSolomonDecoder.cs b/csharp/common/reedsolomon/ReedSolomonDecoder.cs new file mode 100755 index 000000000..0ab74eb88 --- /dev/null +++ b/csharp/common/reedsolomon/ReedSolomonDecoder.cs @@ -0,0 +1,195 @@ +/* +* 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; +namespace com.google.zxing.common.reedsolomon +{ + + ///

Implements Reed-Solomon decoding, as the name implies.

+ /// + ///

The algorithm will not be explained here, but the following references were helpful + /// in creating this implementation:

+ /// + /// + /// + ///

Much credit is due to William Rucklidge since portions of this code are an indirect + /// port of his C++ Reed-Solomon implementation.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + /// William Rucklidge + /// + public sealed class ReedSolomonDecoder + { + private GF256 field; + + public ReedSolomonDecoder(GF256 field) { + this.field = field; + } + + /** + *

Decodes given set of received codewords, which include both data and error-correction + * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place, + * in the input.

+ * + * @param received data and error-correction codewords + * @param twoS number of error-correction codewords available + * @throws ReedSolomonException if decoding fails for any reason + */ + public void decode(int[] received, int twoS) { + try{ + + + GF256Poly poly = new GF256Poly(field, received); + int[] syndromeCoefficients = new int[twoS]; + bool dataMatrix = field.Equals(GF256.DATA_MATRIX_FIELD); + bool noError = true; + for (int i = 0; i < twoS; i++) { + // Thanks to sanfordsquires for this fix: + int eval = poly.evaluateAt(field.exp(dataMatrix ? i + 1 : i)); + syndromeCoefficients[syndromeCoefficients.Length - 1 - i] = eval; + if (eval != 0) { + noError = false; + } + } + if (noError) { + return; + } + GF256Poly syndrome = new GF256Poly(field, syndromeCoefficients); + GF256Poly[] sigmaOmega = + runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS); + GF256Poly sigma = sigmaOmega[0]; + GF256Poly omega = sigmaOmega[1]; + int[] errorLocations = findErrorLocations(sigma); + int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations, dataMatrix); + for (int i = 0; i < errorLocations.Length; i++) { + int position = received.Length - 1 - field.log(errorLocations[i]); + if (position < 0) { + throw new ReedSolomonException("Bad error location"); + } + received[position] = GF256.addOrSubtract(received[position], errorMagnitudes[i]); + } + }catch(ReedSolomonException e){ + throw new ReedSolomonException(e.Message); + } + } + + private GF256Poly[] runEuclideanAlgorithm(GF256Poly a, GF256Poly b, int R){ + // Assume a's degree is >= b's + if (a.getDegree() < b.getDegree()) { + GF256Poly temp = a; + a = b; + b = temp; + } + + GF256Poly rLast = a; + GF256Poly r = b; + GF256Poly sLast = field.getOne(); + GF256Poly s = field.getZero(); + GF256Poly tLast = field.getZero(); + GF256Poly t = field.getOne(); + + // Run Euclidean algorithm until r's degree is less than R/2 + while (r.getDegree() >= R / 2) { + GF256Poly rLastLast = rLast; + GF256Poly sLastLast = sLast; + GF256Poly tLastLast = tLast; + rLast = r; + sLast = s; + tLast = t; + + // Divide rLastLast by rLast, with quotient in q and remainder in r + if (rLast.isZero()) { + // Oops, Euclidean algorithm already terminated? + throw new ReedSolomonException("r_{i-1} was zero"); + } + r = rLastLast; + GF256Poly q = field.getZero(); + int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree()); + int dltInverse = field.inverse(denominatorLeadingTerm); + while (r.getDegree() >= rLast.getDegree() && !r.isZero()) { + int degreeDiff = r.getDegree() - rLast.getDegree(); + int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse); + q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale)); + r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale)); + } + + s = q.multiply(sLast).addOrSubtract(sLastLast); + t = q.multiply(tLast).addOrSubtract(tLastLast); + } + + int sigmaTildeAtZero = t.getCoefficient(0); + if (sigmaTildeAtZero == 0) { + throw new ReedSolomonException("sigmaTilde(0) was zero"); + } + + int inverse = field.inverse(sigmaTildeAtZero); + GF256Poly sigma = t.multiply(inverse); + GF256Poly omega = r.multiply(inverse); + return new GF256Poly[]{sigma, omega}; + } + + private int[] findErrorLocations(GF256Poly errorLocator){ + // This is a direct application of Chien's search + int numErrors = errorLocator.getDegree(); + if (numErrors == 1) { // shortcut + return new int[] { errorLocator.getCoefficient(1) }; + } + int[] result = new int[numErrors]; + int e = 0; + for (int i = 1; i < 256 && e < numErrors; i++) { + if (errorLocator.evaluateAt(i) == 0) { + result[e] = field.inverse(i); + e++; + } + } + if (e != numErrors) { + throw new ReedSolomonException("Error locator degree does not match number of roots"); + } + return result; + } + + private int[] findErrorMagnitudes(GF256Poly errorEvaluator, int[] errorLocations, bool dataMatrix) { + // This is directly applying Forney's Formula + int s = errorLocations.Length; + int[] result = new int[s]; + for (int i = 0; i < s; i++) { + int xiInverse = field.inverse(errorLocations[i]); + int denominator = 1; + for (int j = 0; j < s; j++) { + if (i != j) { + denominator = field.multiply(denominator, + GF256.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse))); + } + } + result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse), + field.inverse(denominator)); + // Thanks to sanfordsquires for this fix: + if (dataMatrix) { + result[i] = field.multiply(result[i], xiInverse); + } + } + return result; + } + + } +} \ No newline at end of file diff --git a/csharp/common/reedsolomon/ReedSolomonEncoder.cs b/csharp/common/reedsolomon/ReedSolomonEncoder.cs new file mode 100755 index 000000000..db76156e1 --- /dev/null +++ b/csharp/common/reedsolomon/ReedSolomonEncoder.cs @@ -0,0 +1,70 @@ +/* +* 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; + +namespace com.google.zxing.common.reedsolomon +{ + public sealed class ReedSolomonEncoder + { + private GF256 Field; + private ArrayList cachedGenerators; + + public ReedSolomonEncoder(GF256 field) { + if (!GF256.QR_CODE_FIELD.Equals(field)) { + throw new ArgumentException("Only QR Code is supported at this time"); + } + this.Field = field; + this.cachedGenerators = new ArrayList(); + cachedGenerators.Add(new GF256Poly(field, new int[] { 1 })); + } + + private GF256Poly buildGenerator(int degree) { + if (degree >= cachedGenerators.Count) { + GF256Poly lastGenerator = (GF256Poly)cachedGenerators[(cachedGenerators.Count - 1)]; + for (int d = cachedGenerators.Count; d <= degree; d++) + { + GF256Poly nextGenerator = lastGenerator.multiply(new GF256Poly(Field, new int[] { 1, Field.exp(d - 1) })); + cachedGenerators.Add(nextGenerator); + lastGenerator = nextGenerator; + } + } + return (GF256Poly) cachedGenerators[(degree)]; + } + + public void encode(int[] toEncode, int ecBytes) { + if (ecBytes == 0) { + throw new ArgumentException("No error correction bytes"); + } + int dataBytes = toEncode.Length - ecBytes; + if (dataBytes <= 0) { + throw new ArgumentException("No data bytes provided"); + } + GF256Poly generator = buildGenerator(ecBytes); + int[] infoCoefficients = new int[dataBytes]; + System.Array.Copy(toEncode, 0, infoCoefficients, 0, dataBytes); + GF256Poly info = new GF256Poly(this.Field, infoCoefficients); + info = info.multiplyByMonomial(ecBytes, 1); + GF256Poly remainder = info.divide(generator)[1]; + int[] coefficients = remainder.getCoefficients(); + int numZeroCoefficients = ecBytes - coefficients.Length; + for (int i = 0; i < numZeroCoefficients; i++) { + toEncode[dataBytes + i] = 0; + } + System.Array.Copy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.Length); + } + + } +} \ No newline at end of file diff --git a/csharp/common/reedsolomon/ReedSolomonException.cs b/csharp/common/reedsolomon/ReedSolomonException.cs new file mode 100755 index 000000000..e96533901 --- /dev/null +++ b/csharp/common/reedsolomon/ReedSolomonException.cs @@ -0,0 +1,38 @@ +/* +* 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; +namespace com.google.zxing.common.reedsolomon +{ + + ///

Thrown when an exception occurs during Reed-Solomon decoding, such as when + /// there are too many errors to correct.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + //[Serializable] + public sealed class ReedSolomonException : System.Exception + { + + public ReedSolomonException() + { + } + + public ReedSolomonException(System.String message) + : base(message) + { + } + } +} \ No newline at end of file diff --git a/csharp/oned/AbstractOneDReader.cs b/csharp/oned/AbstractOneDReader.cs new file mode 100755 index 000000000..4ffd611dc --- /dev/null +++ b/csharp/oned/AbstractOneDReader.cs @@ -0,0 +1,231 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using System; + using com.google.zxing.common; + + public abstract class AbstractOneDReader + { + private static int INTEGER_MATH_SHIFT = 8; + public static int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT; + + public Result decode(MonochromeBitmapSource image) { + return decode(image, null); + } + + public Result decode(MonochromeBitmapSource image, System.Collections.Hashtable hints) { + try { + return doDecode(image, hints); + } catch (ReaderException re) { + bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER); + if (tryHarder && image.isRotateSupported()) { + MonochromeBitmapSource rotatedImage = image.rotateCounterClockwise(); + Result result = doDecode(rotatedImage, hints); + // Record that we found it rotated 90 degrees CCW / 270 degrees CW + System.Collections.Hashtable metadata = result.getResultMetadata(); + int orientation = 270; + if (metadata != null && metadata.ContainsKey(ResultMetadataType.ORIENTATION)) { + // But if we found it reversed in doDecode(), add in that result here: + orientation = (orientation + ((int) metadata[ResultMetadataType.ORIENTATION])) % 360; + } + result.putMetadata(ResultMetadataType.ORIENTATION, orientation); + return result; + } else { + throw re; + } + } + } + + /** + * We're going to examine rows from the middle outward, searching alternately above and below the + * middle, and farther out each time. rowStep is the number of rows between each successive + * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then + * middle + rowStep, then middle - (2 * rowStep), etc. + * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily + * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the + * image if "trying harder". + * + * @param image The image to decode + * @param hints Any hints that were requested + * @return The contents of the decoded barcode + * @throws ReaderException Any spontaneous errors which occur + */ + private Result doDecode(MonochromeBitmapSource image, System.Collections.Hashtable hints) { + int width = image.getWidth(); + int height = image.getHeight(); + BitArray row = new BitArray(width); + + int middle = height >> 1; + bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER); + int rowStep = Math.Max(1, height >> (tryHarder ? 7 : 4)); + int MaxLines; + if (tryHarder) { + MaxLines = height; // Look at the whole image, not just the center + } else { + MaxLines = 9; // Nine rows spaced 1/16 apart is roughly the middle half of the image + } + + for (int x = 0; x < MaxLines; x++) { + + // Scanning from the middle out. Determine which row we're looking at next: + int rowStepsAboveOrBelow = (x + 1) >> 1; + bool isAbove = (x & 0x01) == 0; // i.e. is x even? + int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow); + if (rowNumber < 0 || rowNumber >= height) { + // Oops, if we run off the top or bottom, stop + break; + } + + // Estimate black point for this row and load it: + try { + image.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, rowNumber); + } catch (ReaderException re) { + continue; + } + + image.getBlackRow(rowNumber, row,0, width); + + // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to + // handle decoding upside down barcodes. + for (int attempt = 0; attempt < 2; attempt++) { + if (attempt == 1) { // trying again? + row.reverse(); // reverse the row and continue + } + try { + // Look for a barcode + Result result = decodeRow(rowNumber, row, hints); + // We found our barcode + if (attempt == 1) { + // But it was upside down, so note that + result.putMetadata(ResultMetadataType.ORIENTATION, 180); + // And remember to flip the result points horizontally. + ResultPoint[] points = result.getResultPoints(); + points[0] = (ResultPoint) new GenericResultPoint(width - points[0].getX() - 1, points[0].getY()); + points[1] = (ResultPoint)new GenericResultPoint(width - points[1].getX() - 1, points[1].getY()); + } + return result; + } catch (ReaderException re) { + // continue -- just couldn't decode this row + } + } + } + + throw new ReaderException(); + } + + /** + * Records the size of successive runs of white and black pixels in a row, starting at a given point. + * The values are recorded in the given array, and the number of runs recorded is equal to the size + * of the array. If the row starts on a white pixel at the given start point, then the first count + * recorded is the run of white pixels starting from that point; likewise it is the count of a run + * of black pixels if the row begin on a black pixels at that point. + * + * @param row row to count from + * @param start offset into row to start at + * @param counters array into which to record counts + * @throws ReaderException if counters cannot be filled entirely from row before running out of pixels + */ + public static void recordPattern(BitArray row, int start, int[] counters) { + int numCounters = counters.Length; + for (int i = 0; i < numCounters; i++) { + counters[i] = 0; + } + int end = row.getSize(); + if (start >= end) { + throw new ReaderException(); + } + bool isWhite = !row.get(start); + int counterPosition = 0; + + int k = start; + while (k < end) { + bool pixel = row.get(k); + if ((!pixel && isWhite) || (pixel && !isWhite)) { + counters[counterPosition]++; + } else { + counterPosition++; + if (counterPosition == numCounters) { + break; + } else { + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + k++; + } + // If we read fully the last section of pixels and filled up our counters -- or filled + // the last counter but ran off the side of the image, OK. Otherwise, a problem. + if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && k == end))) { + throw new ReaderException(); + } + } + + /** + * Determines how closely a set of observed counts of runs of black/white values matches a given + * target pattern. This is reported as the ratio of the total variance from the expected pattern + * proportions across all pattern elements, to the length of the pattern. + * + * @param counters observed counters + * @param pattern expected pattern + * @param MaxIndividualVariance The most any counter can differ before we give up + * @return ratio of total variance between counters and pattern compared to total pattern size, + * where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means + * the total variance between counters and patterns equals the pattern length, higher values mean + * even more variance + */ + public static int patternMatchVariance(int[] counters, int[] pattern, int MaxIndividualVariance) { + int numCounters = counters.Length; + int total = 0; + int patternLength = 0; + for (int i = 0; i < numCounters; i++) { + total += counters[i]; + patternLength += pattern[i]; + } + if (total < patternLength) { + // If we don't even have one pixel per unit of bar width, assume this is too small + // to reliably match, so fail: + return int.MaxValue; + } + // We're going to fake floating-point math in integers. We just need to use more bits. + // Scale up patternLength so that intermediate values below like scaledCounter will have + // more "significant digits" + int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength; + MaxIndividualVariance = (MaxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT; + + int totalVariance = 0; + for (int x = 0; x < numCounters; x++) { + int counter = counters[x] << INTEGER_MATH_SHIFT; + int scaledPattern = pattern[x] * unitBarWidth; + int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter; + if (variance > MaxIndividualVariance) { + return int.MaxValue; + } + totalVariance += variance; + } + return totalVariance / total; + } + + // This declaration should not be necessary, since this class is + // abstract and so does not have to provide an implementation for every + // method of an interface it implements, but it is causing NoSuchMethodError + // issues on some Nokia JVMs. So we add this superfluous declaration: + + public abstract Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints); + + + } +} \ No newline at end of file diff --git a/csharp/oned/AbstractUPCEANReader.cs b/csharp/oned/AbstractUPCEANReader.cs new file mode 100755 index 000000000..ef1807820 --- /dev/null +++ b/csharp/oned/AbstractUPCEANReader.cs @@ -0,0 +1,295 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using System; + using System.Text; + using com.google.zxing.common; + + /** + *

Encapsulates functionality and implementation that is common to UPC and EAN families + * of one-dimensional barcodes.

+ * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author alasdair@google.com (Alasdair Mackintosh) + */ + + public abstract class AbstractUPCEANReader : AbstractOneDReader,UPCEANReader + { + private static int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f); + private static int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f); + + /** + * Start/end guard pattern. + */ + private static int[] START_END_PATTERN = {1, 1, 1,}; + + /** + * Pattern marking the middle of a UPC/EAN pattern, separating the two halves. + */ + public static int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1}; + + /** + * "Odd", or "L" patterns used to encode UPC/EAN digits. + */ + public static int[][] L_PATTERNS = new int[][]{ + new int[]{3, 2, 1, 1}, // 0 + new int[]{2, 2, 2, 1}, // 1 + new int[]{2, 1, 2, 2}, // 2 + new int[]{1, 4, 1, 1}, // 3 + new int[]{1, 1, 3, 2}, // 4 + new int[]{1, 2, 3, 1}, // 5 + new int[]{1, 1, 1, 4}, // 6 + new int[]{1, 3, 1, 2}, // 7 + new int[]{1, 2, 1, 3}, // 8 + new int[]{3, 1, 1, 2} // 9 + }; + + /** + * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits. + */ + public static int[][] L_AND_G_PATTERNS=new int[20][]; + + //static { + // L_AND_G_PATTERNS = new int[20][]; + // for (int i = 0; i < 10; i++) { + // L_AND_G_PATTERNS[i] = L_PATTERNS[i]; + // } + // for (int i = 10; i < 20; i++) { + // int[] widths = L_PATTERNS[i - 10]; + // int[] reversedWidths = new int[widths.length]; + // for (int j = 0; j < widths.length; j++) { + // reversedWidths[j] = widths[widths.length - j - 1]; + // } + // L_AND_G_PATTERNS[i] = reversedWidths; + // } + //} + + private StringBuilder decodeRowStringBuffer; + + protected AbstractUPCEANReader() { + for (int i = 0; i < 10; i++) { + L_AND_G_PATTERNS[i] = L_PATTERNS[i]; + } + for (int i = 10; i < 20; i++) { + int[] widths = L_PATTERNS[i - 10]; + int[] reversedWidths = new int[widths.Length]; + for (int j = 0; j < widths.Length; j++) { + reversedWidths[j] = widths[widths.Length - j - 1]; + } + L_AND_G_PATTERNS[i] = reversedWidths; + } + decodeRowStringBuffer = new StringBuilder(20); + } + + public static int[] findStartGuardPattern(BitArray row) { + bool foundStart = false; + int[] startRange = null; + int nextStart = 0; + while (!foundStart) { + startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN); + int start = startRange[0]; + nextStart = startRange[1]; + // Make sure there is a quiet zone at least as big as the start pattern before the barcode. If + // this check would run off the left edge of the image, do not accept this barcode, as it is + // very likely to be a false positive. + int quietStart = start - (nextStart - start); + if (quietStart >= 0) { + foundStart = row.isRange(quietStart, start, false); + } + } + return startRange; + } + + public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) { + return decodeRow(rowNumber, row, findStartGuardPattern(row)); + } + + public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) { + StringBuilder result = decodeRowStringBuffer; + result.Length = 0; + int endStart = decodeMiddle(row, startGuardRange, result); + int[] endRange = decodeEnd(row, endStart); + + // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The + // spec might want more whitespace, but in practice this is the maximum we can count on. + int end = endRange[1]; + int quietEnd = end + (end - endRange[0]); + if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) { + throw new ReaderException(); + } + + String resultString = result.ToString(); + if (!checkChecksum(resultString)) { + throw new ReaderException(); + } + + float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f; + float right = (float) (endRange[1] + endRange[0]) / 2.0f; + return new Result(resultString, + null, // no natural byte representation for these barcodes + new ResultPoint[]{ + new GenericResultPoint(left, (float) rowNumber), + new GenericResultPoint(right, (float) rowNumber)}, + getBarcodeFormat()); + } + + public abstract BarcodeFormat getBarcodeFormat(); + + /** + * @return {@link #checkStandardUPCEANChecksum(String)} + */ + public bool checkChecksum(String s) { + return checkStandardUPCEANChecksum(s); + } + + /** + * Computes the UPC/EAN checksum on a string of digits, and reports + * whether the checksum is correct or not. + * + * @param s string of digits to check + * @return true iff string of digits passes the UPC/EAN checksum algorithm + * @throws ReaderException if the string does not contain only digits + */ + public static bool checkStandardUPCEANChecksum(String s) { + int length = s.Length; + if (length == 0) { + return false; + } + + int sum = 0; + for (int i = length - 2; i >= 0; i -= 2) { + int digit = (int) s[i] - (int) '0'; + if (digit < 0 || digit > 9) { + throw new ReaderException(); + } + sum += digit; + } + sum *= 3; + for (int i = length - 1; i >= 0; i -= 2) { + int digit = (int) s[i] - (int) '0'; + if (digit < 0 || digit > 9) { + throw new ReaderException(); + } + sum += digit; + } + return sum % 10 == 0; + } + + /** + * Subclasses override this to decode the portion of a barcode between the start and end guard patterns. + * + * @param row row of black/white values to search + * @param startRange start/end offset of start guard pattern + * @param resultString {@link StringBuffer} to append decoded chars to + * @return horizontal offset of first pixel after the "middle" that was decoded + * @throws ReaderException if decoding could not complete successfully + */ + protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString); + + int[] decodeEnd(BitArray row, int endStart) { + return findGuardPattern(row, endStart, false, START_END_PATTERN); + } + + /** + * @param row row of black/white values to search + * @param rowOffset position to start search + * @param whiteFirst if true, indicates that the pattern specifies white/black/white/... + * pixel counts, otherwise, it is interpreted as black/white/black/... + * @param pattern pattern of counts of number of black and white pixels that are being + * searched for as a pattern + * @return start/end horizontal offset of guard pattern, as an array of two ints + * @throws ReaderException if pattern is not found + */ + public static int[] findGuardPattern(BitArray row, int rowOffset, bool whiteFirst, int[] pattern) + { + int patternLength = pattern.Length; + int[] counters = new int[patternLength]; + int width = row.getSize(); + bool isWhite = false; + while (rowOffset < width) { + isWhite = !row.get(rowOffset); + if (whiteFirst == isWhite) { + break; + } + rowOffset++; + } + + int counterPosition = 0; + int patternStart = rowOffset; + for (int x = rowOffset; x < width; x++) { + bool pixel = row.get(x); + if ((!pixel && isWhite) || (pixel && !isWhite)) { + counters[counterPosition]++; + } else { + if (counterPosition == patternLength - 1) { + if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { + return new int[]{patternStart, x}; + } + patternStart += counters[0] + counters[1]; + for (int y = 2; y < patternLength; y++) { + counters[y - 2] = counters[y]; + } + counters[patternLength - 2] = 0; + counters[patternLength - 1] = 0; + counterPosition--; + } else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new ReaderException(); + } + + /** + * Attempts to decode a single UPC/EAN-encoded digit. + * + * @param row row of black/white values to decode + * @param counters the counts of runs of observed black/white/black/... values + * @param rowOffset horizontal offset to start decoding from + * @param patterns the set of patterns to use to decode -- sometimes different encodings + * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should + * be used + * @return horizontal offset of first pixel beyond the decoded digit + * @throws ReaderException if digit cannot be decoded + */ + public static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns) + { + recordPattern(row, rowOffset, counters); + int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept + int bestMatch = -1; + int max = patterns.Length; + for (int i = 0; i < max; i++) { + int[] pattern = patterns[i]; + int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = i; + } + } + if (bestMatch >= 0) { + return bestMatch; + } else { + throw new ReaderException(); + } + } + + + } + + +} \ No newline at end of file diff --git a/csharp/oned/Code128Reader.cs b/csharp/oned/Code128Reader.cs new file mode 100755 index 000000000..9834a85ec --- /dev/null +++ b/csharp/oned/Code128Reader.cs @@ -0,0 +1,459 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using System; + using System.Text; + using com.google.zxing.common; + + + public sealed class Code128Reader : AbstractOneDReader + { + private static int[][] CODE_PATTERNS = new int[][]{ + new int[]{2, 1, 2, 2, 2, 2}, // 0 + new int[]{2, 2, 2, 1, 2, 2}, + new int[]{2, 2, 2, 2, 2, 1}, + new int[]{1, 2, 1, 2, 2, 3}, + new int[]{1, 2, 1, 3, 2, 2}, + new int[]{1, 3, 1, 2, 2, 2}, // 5 + new int[]{1, 2, 2, 2, 1, 3}, + new int[]{1, 2, 2, 3, 1, 2}, + new int[]{1, 3, 2, 2, 1, 2}, + new int[]{2, 2, 1, 2, 1, 3}, + new int[]{2, 2, 1, 3, 1, 2}, // 10 + new int[]{2, 3, 1, 2, 1, 2}, + new int[]{1, 1, 2, 2, 3, 2}, + new int[]{1, 2, 2, 1, 3, 2}, + new int[]{1, 2, 2, 2, 3, 1}, + new int[]{1, 1, 3, 2, 2, 2}, // 15 + new int[]{1, 2, 3, 1, 2, 2}, + new int[]{1, 2, 3, 2, 2, 1}, + new int[]{2, 2, 3, 2, 1, 1}, + new int[]{2, 2, 1, 1, 3, 2}, + new int[]{2, 2, 1, 2, 3, 1}, // 20 + new int[]{2, 1, 3, 2, 1, 2}, + new int[]{2, 2, 3, 1, 1, 2}, + new int[]{3, 1, 2, 1, 3, 1}, + new int[]{3, 1, 1, 2, 2, 2}, + new int[]{3, 2, 1, 1, 2, 2}, // 25 + new int[]{3, 2, 1, 2, 2, 1}, + new int[]{3, 1, 2, 2, 1, 2}, + new int[]{3, 2, 2, 1, 1, 2}, + new int[]{3, 2, 2, 2, 1, 1}, + new int[]{2, 1, 2, 1, 2, 3}, // 30 + new int[]{2, 1, 2, 3, 2, 1}, + new int[]{2, 3, 2, 1, 2, 1}, + new int[]{1, 1, 1, 3, 2, 3}, + new int[]{1, 3, 1, 1, 2, 3}, + new int[]{1, 3, 1, 3, 2, 1}, // 35 + new int[]{1, 1, 2, 3, 1, 3}, + new int[]{1, 3, 2, 1, 1, 3}, + new int[]{1, 3, 2, 3, 1, 1}, + new int[]{2, 1, 1, 3, 1, 3}, + new int[]{2, 3, 1, 1, 1, 3}, // 40 + new int[]{2, 3, 1, 3, 1, 1}, + new int[]{1, 1, 2, 1, 3, 3}, + new int[]{1, 1, 2, 3, 3, 1}, + new int[]{1, 3, 2, 1, 3, 1}, + new int[]{1, 1, 3, 1, 2, 3}, // 45 + new int[]{1, 1, 3, 3, 2, 1}, + new int[]{1, 3, 3, 1, 2, 1}, + new int[]{3, 1, 3, 1, 2, 1}, + new int[]{2, 1, 1, 3, 3, 1}, + new int[]{2, 3, 1, 1, 3, 1}, // 50 + new int[]{2, 1, 3, 1, 1, 3}, + new int[]{2, 1, 3, 3, 1, 1}, + new int[]{2, 1, 3, 1, 3, 1}, + new int[]{3, 1, 1, 1, 2, 3}, + new int[]{3, 1, 1, 3, 2, 1}, // 55 + new int[]{3, 3, 1, 1, 2, 1}, + new int[]{3, 1, 2, 1, 1, 3}, + new int[]{3, 1, 2, 3, 1, 1}, + new int[]{3, 3, 2, 1, 1, 1}, + new int[]{3, 1, 4, 1, 1, 1}, // 60 + new int[]{2, 2, 1, 4, 1, 1}, + new int[]{4, 3, 1, 1, 1, 1}, + new int[]{1, 1, 1, 2, 2, 4}, + new int[]{1, 1, 1, 4, 2, 2}, + new int[] {1, 2, 1, 1, 2, 4}, // 65 + new int[]{1, 2, 1, 4, 2, 1}, + new int[]{1, 4, 1, 1, 2, 2}, + new int[]{1, 4, 1, 2, 2, 1}, + new int[]{1, 1, 2, 2, 1, 4}, + new int[]{1, 1, 2, 4, 1, 2}, // 70 + new int[]{1, 2, 2, 1, 1, 4}, + new int[]{1, 2, 2, 4, 1, 1}, + new int[]{1, 4, 2, 1, 1, 2}, + new int[]{1, 4, 2, 2, 1, 1}, + new int[]{2, 4, 1, 2, 1, 1}, // 75 + new int[]{2, 2, 1, 1, 1, 4}, + new int[]{4, 1, 3, 1, 1, 1}, + new int[]{2, 4, 1, 1, 1, 2}, + new int[]{1, 3, 4, 1, 1, 1}, + new int[]{1, 1, 1, 2, 4, 2}, // 80 + new int[]{1, 2, 1, 1, 4, 2}, + new int[]{1, 2, 1, 2, 4, 1}, + new int[]{1, 1, 4, 2, 1, 2}, + new int[]{1, 2, 4, 1, 1, 2}, + new int[]{1, 2, 4, 2, 1, 1}, // 85 + new int[]{4, 1, 1, 2, 1, 2}, + new int[]{4, 2, 1, 1, 1, 2}, + new int[]{4, 2, 1, 2, 1, 1}, + new int[]{2, 1, 2, 1, 4, 1}, + new int[]{2, 1, 4, 1, 2, 1}, // 90 + new int[]{4, 1, 2, 1, 2, 1}, + new int[]{1, 1, 1, 1, 4, 3}, + new int[]{1, 1, 1, 3, 4, 1}, + new int[]{1, 3, 1, 1, 4, 1}, + new int[]{1, 1, 4, 1, 1, 3}, // 95 + new int[]{1, 1, 4, 3, 1, 1}, + new int[]{4, 1, 1, 1, 1, 3}, + new int[]{4, 1, 1, 3, 1, 1}, + new int[]{1, 1, 3, 1, 4, 1}, + new int[]{1, 1, 4, 1, 3, 1}, // 100 + new int[]{3, 1, 1, 1, 4, 1}, + new int[]{4, 1, 1, 1, 3, 1}, + new int[]{2, 1, 1, 4, 1, 2}, + new int[]{2, 1, 1, 2, 1, 4}, + new int[]{2, 1, 1, 2, 3, 2}, // 105 + new int[]{2, 3, 3, 1, 1, 1, 2} + }; + + private static int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.25f); + private static int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f); + + private const int CODE_SHIFT = 98; + + private const int CODE_CODE_C = 99; + private const int CODE_CODE_B = 100; + private const int CODE_CODE_A = 101; + + private const int CODE_FNC_1 = 102; + private const int CODE_FNC_2 = 97; + private const int CODE_FNC_3 = 96; + private const int CODE_FNC_4_A = 101; + private const int CODE_FNC_4_B = 100; + + private const int CODE_START_A = 103; + private const int CODE_START_B = 104; + private const int CODE_START_C = 105; + private const int CODE_STOP = 106; + + private static int[] findStartPattern(BitArray row) { + int width = row.getSize(); + int rowOffset = 0; + while (rowOffset < width) { + if (row.get(rowOffset)) { + break; + } + rowOffset++; + } + + int counterPosition = 0; + int[] counters = new int[6]; + int patternStart = rowOffset; + bool isWhite = false; + int patternLength = counters.Length; + + for (int i = rowOffset; i < width; i++) { + bool pixel = row.get(i); + if ((!pixel && isWhite) || (pixel && !isWhite)) { + counters[counterPosition]++; + } else { + if (counterPosition == patternLength - 1) { + int bestVariance = MAX_AVG_VARIANCE; + int bestMatch = -1; + for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) { + int variance = patternMatchVariance(counters, CODE_PATTERNS[startCode], MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = startCode; + } + } + if (bestMatch >= 0) { + // Look for whitespace before start pattern, >= 50% of width of start pattern + if (row.isRange(Math.Max(0, patternStart - (i - patternStart) / 2), patternStart, false)) { + return new int[]{patternStart, i, bestMatch}; + } + } + patternStart += counters[0] + counters[1]; + for (int y = 2; y < patternLength; y++) { + counters[y - 2] = counters[y]; + } + counters[patternLength - 2] = 0; + counters[patternLength - 1] = 0; + counterPosition--; + } else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new ReaderException(); + } + + private static int decodeCode(BitArray row, int[] counters, int rowOffset) { + recordPattern(row, rowOffset, counters); + int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept + int bestMatch = -1; + for (int d = 0; d < CODE_PATTERNS.Length; d++) { + int[] pattern = CODE_PATTERNS[d]; + int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = d; + } + } + // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6 + if (bestMatch >= 0) { + return bestMatch; + } else { + throw new ReaderException(); + } + } + + public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) { + + int[] startPatternInfo = findStartPattern(row); + int startCode = startPatternInfo[2]; + int codeSet; + switch (startCode) { + case CODE_START_A: + codeSet = CODE_CODE_A; + break; + case CODE_START_B: + codeSet = CODE_CODE_B; + break; + case CODE_START_C: + codeSet = CODE_CODE_C; + break; + default: + throw new ReaderException(); + } + + bool done = false; + bool isNextShifted = false; + + StringBuilder result = new StringBuilder(); + int lastStart = startPatternInfo[0]; + int nextStart = startPatternInfo[1]; + int[] counters = new int[6]; + + int lastCode = 0; + int code = 0; + int checksumTotal = startCode; + int multiplier = 0; + bool lastCharacterWasPrintable = true; + + while (!done) { + + bool unshift = isNextShifted; + isNextShifted = false; + + // Save off last code + lastCode = code; + + // Decode another code from image + code = decodeCode(row, counters, nextStart); + + // Remember whether the last code was printable or not (excluding CODE_STOP) + if (code != CODE_STOP) { + lastCharacterWasPrintable = true; + } + + // Add to checksum computation (if not CODE_STOP of course) + if (code != CODE_STOP) { + multiplier++; + checksumTotal += multiplier * code; + } + + // Advance to where the next code will to start + lastStart = nextStart; + for (int i = 0; i < counters.Length; i++) { + nextStart += counters[i]; + } + + // Take care of illegal start codes + switch (code) { + case CODE_START_A: + case CODE_START_B: + case CODE_START_C: + throw new ReaderException(); + } + + switch (codeSet) { + + case CODE_CODE_A: + if (code < 64) { + result.Append((char) (' ' + code)); + } else if (code < 96) { + result.Append((char) (code - 64)); + } else { + // Don't let CODE_STOP, which always appears, affect whether whether we think the last code + // was printable or not + if (code != CODE_STOP) { + lastCharacterWasPrintable = false; + } + switch (code) { + case CODE_FNC_1: + case CODE_FNC_2: + case CODE_FNC_3: + case CODE_FNC_4_A: + // do nothing? + break; + case CODE_SHIFT: + isNextShifted = true; + codeSet = CODE_CODE_B; + break; + case CODE_CODE_B: + codeSet = CODE_CODE_B; + break; + case CODE_CODE_C: + codeSet = CODE_CODE_C; + break; + case CODE_STOP: + done = true; + break; + } + } + break; + case CODE_CODE_B: + if (code < 96) { + result.Append((char) (' ' + code)); + } else { + if (code != CODE_STOP) { + lastCharacterWasPrintable = false; + } + switch (code) { + case CODE_FNC_1: + case CODE_FNC_2: + case CODE_FNC_3: + case CODE_FNC_4_B: + // do nothing? + break; + case CODE_SHIFT: + isNextShifted = true; + codeSet = CODE_CODE_C; + break; + case CODE_CODE_A: + codeSet = CODE_CODE_A; + break; + case CODE_CODE_C: + codeSet = CODE_CODE_C; + break; + case CODE_STOP: + done = true; + break; + } + } + break; + case CODE_CODE_C: + if (code < 100) { + if (code < 10) { + result.Append('0'); + } + result.Append(code); + } else { + if (code != CODE_STOP) { + lastCharacterWasPrintable = false; + } + switch (code) { + case CODE_FNC_1: + // do nothing? + break; + case CODE_CODE_A: + codeSet = CODE_CODE_A; + break; + case CODE_CODE_B: + codeSet = CODE_CODE_B; + break; + case CODE_STOP: + done = true; + break; + } + } + break; + } + + // Unshift back to another code set if we were shifted + if (unshift) { + switch (codeSet) { + case CODE_CODE_A: + codeSet = CODE_CODE_C; + break; + case CODE_CODE_B: + codeSet = CODE_CODE_A; + break; + case CODE_CODE_C: + codeSet = CODE_CODE_B; + break; + } + } + + } + + // Check for ample whitespice following pattern, but, to do this we first need to remember that we + // fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left to read off. + // Would be slightly better to properly read. Here we just skip it: + while (row.get(nextStart)) { + nextStart++; + } + if (!row.isRange(nextStart, Math.Min(row.getSize(), nextStart + (nextStart - lastStart) / 2), false)) { + throw new ReaderException(); + } + + // Pull out from sum the value of the penultimate check code + checksumTotal -= multiplier * lastCode; + // lastCode is the checksum then: + if (checksumTotal % 103 != lastCode) { + throw new ReaderException(); + } + + // Need to pull out the check digits from string + int resultLength = result.Length; + // Only bother if, well, the result had at least one character, and if the checksum digit happened + // to be a printable character. If it was just interpreted as a control code, nothing to remove + if (resultLength > 0 && lastCharacterWasPrintable) { + if (codeSet == CODE_CODE_C) { + result.Remove(resultLength - 2, resultLength); + } else { + result.Remove(resultLength - 1, resultLength); + } + } + + String resultString = result.ToString(); + + if (resultString.Length == 0) { + // Almost surely a false positive + throw new ReaderException(); + } + + float left = (float) (startPatternInfo[1] + startPatternInfo[0]) / 2.0f; + float right = (float) (nextStart + lastStart) / 2.0f; + return new Result( + resultString, + null, + new ResultPoint[]{ + new GenericResultPoint(left, (float) rowNumber), + new GenericResultPoint(right, (float) rowNumber)}, + BarcodeFormat.CODE_128); + + } + + } + + + +} \ No newline at end of file diff --git a/csharp/oned/Code39Reader.cs b/csharp/oned/Code39Reader.cs new file mode 100755 index 000000000..ebddc97e4 --- /dev/null +++ b/csharp/oned/Code39Reader.cs @@ -0,0 +1,319 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using System; + using System.Text; + using com.google.zxing.common; + + + public sealed class Code39Reader : AbstractOneDReader + { + private static String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%"; + private static char[] ALPHABET = ALPHABET_STRING.ToCharArray(); + + /** + * These represent the encodings of characters, as patterns of wide and narrow bars. + * The 9 least-significant bits of each int correspond to the pattern of wide and narrow, + * with 1s representing "wide" and 0s representing narrow. + */ + private static int[] CHARACTER_ENCODINGS = { + 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9 + 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J + 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T + 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x094, // U-* + 0x0A8, 0x0A2, 0x08A, 0x02A // $-% + }; + + private static int ASTERISK_ENCODING = CHARACTER_ENCODINGS[39]; + + private bool usingCheckDigit; + private bool extendedMode; + + /** + * Creates a reader that assumes all encoded data is data, and does not treat the + * character as a check digit. It will not decoded "extended Code 39" sequences. + */ + public Code39Reader() { + usingCheckDigit = false; + extendedMode = false; + } + + /** + * Creates a reader that can be configured to check the last character as a check digit. + * It will not decoded "extended Code 39" sequences. + * + * @param usingCheckDigit if true, treat the last data character as a check digit, not + * data, and verify that the checksum passes. + */ + public Code39Reader(bool usingCheckDigit) { + this.usingCheckDigit = usingCheckDigit; + this.extendedMode = false; + } + + /** + * Creates a reader that can be configured to check the last character as a check digit, + * or optionally attempt to decode "extended Code 39" sequences that are used to encode + * the full ASCII character set. + * + * @param usingCheckDigit if true, treat the last data character as a check digit, not + * data, and verify that the checksum passes. + * @param extendedMode if true, will attempt to decode extended Code 39 sequences in the + * text. + */ + public Code39Reader(bool usingCheckDigit, bool extendedMode) { + this.usingCheckDigit = usingCheckDigit; + this.extendedMode = extendedMode; + } + + public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) { + + int[] start = findAsteriskPattern(row); + int nextStart = start[1]; + int end = row.getSize(); + + // Read off white space + while (nextStart < end && !row.get(nextStart)) { + nextStart++; + } + + StringBuilder result = new StringBuilder(); + int[] counters = new int[9]; + char decodedChar; + int lastStart; + do { + recordPattern(row, nextStart, counters); + int pattern = toNarrowWidePattern(counters); + decodedChar = patternToChar(pattern); + result.Append(decodedChar); + lastStart = nextStart; + for (int i = 0; i < counters.Length; i++) { + nextStart += counters[i]; + } + // Read off white space + while (nextStart < end && !row.get(nextStart)) { + nextStart++; + } + } while (decodedChar != '*'); + + result.Remove(result.Length - 1, 1); // remove asterisk + + // Look for whitespace after pattern: + int lastPatternSize = 0; + for (int i = 0; i < counters.Length; i++) { + lastPatternSize += counters[i]; + } + int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize; + // If 50% of last pattern size, following last pattern, is not whitespace, fail + // (but if it's whitespace to the very end of the image, that's OK) + if (nextStart != end && whiteSpaceAfterEnd / 2 < lastPatternSize) { + throw new ReaderException(); + } + + if (usingCheckDigit) { + int max = result.Length - 1; + int total = 0; + for (int i = 0; i < max; i++) { + total += ALPHABET_STRING.IndexOf(result[i]); + } + if (total % 43 != ALPHABET_STRING.IndexOf(result[max])) + { + throw new ReaderException(); + } + result.Remove(max,1); + } + + String resultString = result.ToString(); + if (extendedMode) { + resultString = decodeExtended(resultString); + } + + if (resultString.Length == 0) { + // Almost surely a false positive + throw new ReaderException(); + } + + float left = (float) (start[1] + start[0]) / 2.0f; + float right = (float) (nextStart + lastStart) / 2.0f; + return new Result( + resultString, + null, + new ResultPoint[]{ + new GenericResultPoint(left, (float) rowNumber), + new GenericResultPoint(right, (float) rowNumber)}, + BarcodeFormat.CODE_39); + + } + + private static int[] findAsteriskPattern(BitArray row) { + int width = row.getSize(); + int rowOffset = 0; + while (rowOffset < width) { + if (row.get(rowOffset)) { + break; + } + rowOffset++; + } + + int counterPosition = 0; + int[] counters = new int[9]; + int patternStart = rowOffset; + bool isWhite = false; + int patternLength = counters.Length; + + for (int i = rowOffset; i < width; i++) { + bool pixel = row.get(i); + if ((!pixel && isWhite) || (pixel && !isWhite)) { + counters[counterPosition]++; + } else { + if (counterPosition == patternLength - 1) { + try { + if (toNarrowWidePattern(counters) == ASTERISK_ENCODING) { + // Look for whitespace before start pattern, >= 50% of width of start pattern + if (row.isRange(Math.Max(0, patternStart - (i - patternStart) / 2), patternStart, false)) { + return new int[]{patternStart, i}; + } + } + } catch (ReaderException re) { + // no match, continue + } + patternStart += counters[0] + counters[1]; + for (int y = 2; y < patternLength; y++) { + counters[y - 2] = counters[y]; + } + counters[patternLength - 2] = 0; + counters[patternLength - 1] = 0; + counterPosition--; + } else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new ReaderException(); + } + + private static int toNarrowWidePattern(int[] counters) { + int numCounters = counters.Length; + int maxNarrowCounter = 0; + int wideCounters; + do { + int minCounter = int.MaxValue; + for (int i = 0; i < numCounters; i++) { + int counter = counters[i]; + if (counter < minCounter && counter > maxNarrowCounter) { + minCounter = counter; + } + } + maxNarrowCounter = minCounter; + wideCounters = 0; + int totalWideCountersWidth = 0; + int pattern = 0; + for (int i = 0; i < numCounters; i++) { + int counter = counters[i]; + if (counters[i] > maxNarrowCounter) { + pattern |= 1 << (numCounters - 1 - i); + wideCounters++; + totalWideCountersWidth += counter; + } + } + if (wideCounters == 3) { + // Found 3 wide counters, but are they close enough in width? + // We can perform a cheap, conservative check to see if any individual + // counter is more than 1.5 times the average: + for (int i = 0; i < numCounters && wideCounters > 0; i++) { + int counter = counters[i]; + if (counters[i] > maxNarrowCounter) { + wideCounters--; + // totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average + if ((counter << 1) >= totalWideCountersWidth) { + throw new ReaderException(); + } + } + } + return pattern; + } + } while (wideCounters > 3); + throw new ReaderException(); + } + + private static char patternToChar(int pattern) { + for (int i = 0; i < CHARACTER_ENCODINGS.Length; i++) { + if (CHARACTER_ENCODINGS[i] == pattern) { + return ALPHABET[i]; + } + } + throw new ReaderException(); + } + + private static String decodeExtended(String encoded) { + int Length = encoded.Length; + StringBuilder decoded = new StringBuilder(Length); + for (int i = 0; i < Length; i++) { + char c = encoded[i]; + if (c == '+' || c == '$' || c == '%' || c == '/') { + char next = encoded[i + 1]; + char decodedChar = '\0'; + switch (c) { + case '+': + // +A to +Z map to a to z + if (next >= 'A' && next <= 'Z') { + decodedChar = (char) (next + 32); + } else { + throw new ReaderException(); + } + break; + case '$': + // $A to $Z map to control codes SH to SB + if (next >= 'A' && next <= 'Z') { + decodedChar = (char) (next - 64); + } else { + throw new ReaderException(); + } + break; + case '%': + // %A to %E map to control codes ESC to US + if (next >= 'A' && next <= 'E') { + decodedChar = (char) (next - 38); + } else if (next >= 'F' && next <= 'W') { + decodedChar = (char) (next - 11); + } else { + throw new ReaderException(); + } + break; + case '/': + // /A to /O map to ! to , and /Z maps to : + if (next >= 'A' && next <= 'O') { + decodedChar = (char) (next - 32); + } else if (next == 'Z') { + decodedChar = ':'; + } else { + throw new ReaderException(); + } + break; + } + decoded.Append(decodedChar); + // bump up i again since we read two characters + i++; + } else { + decoded.Append(c); + } + } + return decoded.ToString(); + } + + + } +} \ No newline at end of file diff --git a/csharp/oned/EAN13Reader.cs b/csharp/oned/EAN13Reader.cs new file mode 100755 index 000000000..0dda00504 --- /dev/null +++ b/csharp/oned/EAN13Reader.cs @@ -0,0 +1,132 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + /** + *

Implements decoding of the EAN-13 format.

+ * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author alasdair@google.com (Alasdair Mackintosh) + */ + + using System.Text; + using com.google.zxing.common; + + public sealed class EAN13Reader : AbstractUPCEANReader + { + // For an EAN-13 barcode, the first digit is represented by the parities used + // to encode the next six digits, according to the table below. For example, + // if the barcode is 5 123456 789012 then the value of the first digit is + // signified by using odd for '1', even for '2', even for '3', odd for '4', + // odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13 + // + // Parity of next 6 digits + // Digit 0 1 2 3 4 5 + // 0 Odd Odd Odd Odd Odd Odd + // 1 Odd Odd Even Odd Even Even + // 2 Odd Odd Even Even Odd Even + // 3 Odd Odd Even Even Even Odd + // 4 Odd Even Odd Odd Even Even + // 5 Odd Even Even Odd Odd Even + // 6 Odd Even Even Even Odd Odd + // 7 Odd Even Odd Even Odd Even + // 8 Odd Even Odd Even Even Odd + // 9 Odd Even Even Odd Even Odd + // + // Note that the encoding for '0' uses the same parity as a UPC barcode. Hence + // a UPC barcode can be converted to an EAN-13 barcode by prepending a 0. + // + // The encodong is represented by the following array, which is a bit pattern + // using Odd = 0 and Even = 1. For example, 5 is represented by: + // + // Odd Even Even Odd Odd Even + // in binary: + // 0 1 1 0 0 1 == 0x19 + // + private static int[] FIRST_DIGIT_ENCODINGS = { + 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A + }; + + private int[] decodeMiddleCounters; + + public EAN13Reader() { + decodeMiddleCounters = new int[4]; + } + + protected override int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) { + int[] counters = decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + int end = row.getSize(); + int rowOffset = startRange[1]; + + int lgPatternFound = 0; + + for (int x = 0; x < 6 && rowOffset < end; x++) { + int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS); + resultString.Append((char) ('0' + bestMatch % 10)); + for (int i = 0; i < counters.Length; i++) { + rowOffset += counters[i]; + } + if (bestMatch >= 10) { + lgPatternFound |= 1 << (5 - x); + } + } + + determineFirstDigit(resultString, lgPatternFound); + + int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); + rowOffset = middleRange[1]; + + for (int x = 0; x < 6 && rowOffset < end; x++) { + int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); + resultString.Append((char) ('0' + bestMatch)); + for (int i = 0; i < counters.Length; i++) { + rowOffset += counters[i]; + } + } + + return rowOffset; + } + + public override BarcodeFormat getBarcodeFormat() { + return BarcodeFormat.EAN_13; + } + + /** + * Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded digits + * in a barcode, determines the implicitly encoded first digit and adds it to the result string. + * + * @param resultString string to insert decoded first digit into + * @param lgPatternFound int whose bits indicates the pattern of odd/even L/G patterns used to + * encode digits + * @throws ReaderException if first digit cannot be determined + */ + private static void determineFirstDigit(StringBuilder resultString, int lgPatternFound) { + for (int d = 0; d < 10; d++) { + if (lgPatternFound == FIRST_DIGIT_ENCODINGS[d]) { + resultString.Insert(0, (char) ('0' + d)); + return; + } + } + throw new ReaderException(); + } + + + + } +} \ No newline at end of file diff --git a/csharp/oned/EAN8Reader.cs b/csharp/oned/EAN8Reader.cs new file mode 100755 index 000000000..6a21b1952 --- /dev/null +++ b/csharp/oned/EAN8Reader.cs @@ -0,0 +1,64 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using System; + using System.Text; + using com.google.zxing.common; + + public sealed class EAN8Reader : AbstractUPCEANReader + { + private int[] decodeMiddleCounters; + public EAN8Reader() { + decodeMiddleCounters = new int[4]; + } + + protected override int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) { + int[] counters = decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + int end = row.getSize(); + int rowOffset = startRange[1]; + + for (int x = 0; x < 4 && rowOffset < end; x++) { + int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); + result.Append((char) ('0' + bestMatch)); + for (int i = 0; i < counters.Length; i++) { + rowOffset += counters[i]; + } + } + + int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN); + rowOffset = middleRange[1]; + + for (int x = 0; x < 4 && rowOffset < end; x++) { + int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS); + result.Append((char) ('0' + bestMatch)); + for (int i = 0; i < counters.Length; i++) { + rowOffset += counters[i]; + } + } + + return rowOffset; + } + + public override BarcodeFormat getBarcodeFormat() { + return BarcodeFormat.EAN_8; + } + + } + +} \ No newline at end of file diff --git a/csharp/oned/ITFReader.cs b/csharp/oned/ITFReader.cs new file mode 100755 index 000000000..264e23453 --- /dev/null +++ b/csharp/oned/ITFReader.cs @@ -0,0 +1,323 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + /** + *

Implements decoding of the EAN-13 format.

+ * + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + * @author alasdair@google.com (Alasdair Mackintosh) + */ + + using System.Text; + using com.google.zxing.common; + + public sealed class ITFReader : AbstractOneDReader + { + private static int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f); + private static int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f); + + private static int W = 3; // Pixel width of a wide line + private static int N = 1; // Pixed width of a narrow line + + // Stores the actual narrow line width of the image being decoded. + private int narrowLineWidth = -1; + + /** + * Start/end guard pattern. + * + * Note: The end pattern is reversed because the row is reversed before + * searching for the END_PATTERN + */ + private static int[] START_PATTERN = {N, N, N, N}; + private static int[] END_PATTERN_REVERSED = {N, N, W}; + + /** + * Patterns of Wide / Narrow lines to indicate each digit + */ + private static int[][] PATTERNS = new int[][]{ + new int[]{N, N, W, W, N}, // 0 + new int[]{W, N, N, N, W}, // 1 + new int[]{N, W, N, N, W}, // 2 + new int[]{W, W, N, N, N}, // 3 + new int[]{N, N, W, N, W}, // 4 + new int[]{W, N, W, N, N}, // 5 + new int[]{N, W, W, N, N}, // 6 + new int[]{N, N, N, W, W}, // 7 + new int[]{W, N, N, W, N}, // 8 + new int[]{N, W, N, W, N} // 9 + }; + + public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) { + + StringBuilder result = new StringBuilder(20); + + // Find out where the Middle section (payload) starts & ends + int[] startRange = decodeStart(row); + int[] endRange = decodeEnd(row); + + decodeMiddle(row, startRange[1], endRange[0], result); + + string resultString = result.ToString(); + + // To avoid false positives with 2D barcodes (and other patterns), make + // an assumption that the decoded string must be 6, 10 or 14 digits. + int length = resultString.Length; + if (length != 6 && length != 10 && length != 14) { + throw new ReaderException(); + } + + return new Result( + resultString, + null, // no natural byte representation for these barcodes + new ResultPoint[] { new GenericResultPoint(startRange[1], (float) rowNumber), + new GenericResultPoint(startRange[0], (float) rowNumber)}, + BarcodeFormat.ITF); + } + + /** + * @param row row of black/white values to search + * @param payloadStart offset of start pattern + * @param resultString {@link StringBuilder} to Append decoded chars to + * @throws ReaderException if decoding could not complete successfully + */ + static void decodeMiddle(BitArray row, int payloadStart, int payloadEnd, StringBuilder resultString) { + + // Digits are interleaved in pairs - 5 black lines for one digit, and the + // 5 + // interleaved white lines for the second digit. + // Therefore, need to scan 10 lines and then + // split these into two arrays + int[] counterDigitPair = new int[10]; + int[] counterBlack = new int[5]; + int[] counterWhite = new int[5]; + + while (payloadStart < payloadEnd) { + + // Get 10 runs of black/white. + recordPattern(row, payloadStart, counterDigitPair); + // Split them into each array + for (int k = 0; k < 5; k++) { + int twoK = k << 1; + counterBlack[k] = counterDigitPair[twoK]; + counterWhite[k] = counterDigitPair[twoK + 1]; + } + + int bestMatch = decodeDigit(counterBlack); + resultString.Append((char) ('0' + bestMatch)); + bestMatch = decodeDigit(counterWhite); + resultString.Append((char) ('0' + bestMatch)); + + for (int i = 0; i < counterDigitPair.Length; i++) { + payloadStart += counterDigitPair[i]; + } + } + } + + /** + * Identify where the start of the middle / payload section starts. + * + * @param row row of black/white values to search + * @return Array, containing index of start of 'start block' and end of + * 'start block' + * @throws ReaderException + */ + int[] decodeStart(BitArray row) { + int endStart = skipWhiteSpace(row); + int[] startPattern = findGuardPattern(row, endStart, START_PATTERN); + + // Determine the width of a narrow line in pixels. We can do this by + // getting the width of the start pattern and dividing by 4 because its + // made up of 4 narrow lines. + this.narrowLineWidth = (startPattern[1] - startPattern[0]) >> 2; + + validateQuietZone(row, startPattern[0]); + + return startPattern; + } + + /** + * The start & end patterns must be pre/post fixed by a quiet zone. This + * zone must be at least 10 times the width of a narrow line. Scan back until + * we either get to the start of the barcode or match the necessary number of + * quiet zone pixels. + * + * Note: Its assumed the row is reversed when using this method to find + * quiet zone after the end pattern. + * + * ref: http://www.barcode-1.net/i25code.html + * + * @param row bit array representing the scanned barcode. + * @param startPattern index into row of the start or end pattern. + * @throws ReaderException if the quiet zone cannot be found, a ReaderException is thrown. + */ + private void validateQuietZone(BitArray row, int startPattern) { + + int quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone + + for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) { + if (row.get(i)) { + break; + } + quietCount--; + } + if (quietCount != 0) { + // Unable to find the necessary number of quiet zone pixels. + throw new ReaderException(); + } + } + + /** + * Skip all whitespace until we get to the first black line. + * + * @param row row of black/white values to search + * @return index of the first black line. + * @throws ReaderException Throws exception if no black lines are found in the row + */ + private int skipWhiteSpace(BitArray row) { + int width = row.getSize(); + int endStart = 0; + while (endStart < width) { + if (row.get(endStart)) { + break; + } + endStart++; + } + if (endStart == width) { + throw new ReaderException(); + } + + return endStart; + } + + /** + * Identify where the end of the middle / payload section ends. + * + * @param row row of black/white values to search + * @return Array, containing index of start of 'end block' and end of 'end + * block' + * @throws ReaderException + */ + + int[] decodeEnd(BitArray row) { + + // For convenience, reverse the row and then + // search from 'the start' for the end block + row.reverse(); + + int endStart = skipWhiteSpace(row); + int[] endPattern; + try { + endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED); + } catch (ReaderException e) { + // Put our row of data back the right way before throwing + row.reverse(); + throw e; + } + + // The start & end patterns must be pre/post fixed by a quiet zone. This + // zone must be at least 10 times the width of a narrow line. + // ref: http://www.barcode-1.net/i25code.html + validateQuietZone(row, endPattern[0]); + + // Now recalc the indicies of where the 'endblock' starts & stops to + // accomodate + // the reversed nature of the search + int temp = endPattern[0]; + endPattern[0] = row.getSize() - endPattern[1]; + endPattern[1] = row.getSize() - temp; + + // Put the row back the righ way. + row.reverse(); + return endPattern; + } + + /** + * @param row row of black/white values to search + * @param rowOffset position to start search + * @param pattern pattern of counts of number of black and white pixels that are + * being searched for as a pattern + * @return start/end horizontal offset of guard pattern, as an array of two + * ints + * @throws ReaderException if pattern is not found + */ + int[] findGuardPattern(BitArray row, int rowOffset, int[] pattern) { + + // TODO: This is very similar to implementation in AbstractUPCEANReader. Consider if they can be merged to + // a single method. + + int patternLength = pattern.Length; + int[] counters = new int[patternLength]; + int width = row.getSize(); + bool isWhite = false; + + int counterPosition = 0; + int patternStart = rowOffset; + for (int x = rowOffset; x < width; x++) { + bool pixel = row.get(x); + if ((!pixel && isWhite) || (pixel && !isWhite)) { + counters[counterPosition]++; + } else { + if (counterPosition == patternLength - 1) { + if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { + return new int[]{patternStart, x}; + } + patternStart += counters[0] + counters[1]; + for (int y = 2; y < patternLength; y++) { + counters[y - 2] = counters[y]; + } + counters[patternLength - 2] = 0; + counters[patternLength - 1] = 0; + counterPosition--; + } else { + counterPosition++; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + throw new ReaderException(); + } + + /** + * Attempts to decode a sequence of ITF black/white lines into single + * digit. + * + * @param counters the counts of runs of observed black/white/black/... values + * @return The decoded digit + * @throws ReaderException if digit cannot be decoded + */ + private static int decodeDigit(int[] counters) { + + int bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept + int bestMatch = -1; + int max = PATTERNS.Length; + for (int i = 0; i < max; i++) { + int[] pattern = PATTERNS[i]; + int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); + if (variance < bestVariance) { + bestVariance = variance; + bestMatch = i; + } + } + if (bestMatch >= 0) { + return bestMatch; + } else { + throw new ReaderException(); + } + } + + } +} \ No newline at end of file diff --git a/csharp/oned/MultiFormatOneDReader.cs b/csharp/oned/MultiFormatOneDReader.cs new file mode 100755 index 000000000..9bb314b9b --- /dev/null +++ b/csharp/oned/MultiFormatOneDReader.cs @@ -0,0 +1,75 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + /** + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ + using System.Text; + using com.google.zxing.common; + + public sealed class MultiFormatOneDReader : AbstractOneDReader + { + private System.Collections.ArrayList readers; + public MultiFormatOneDReader(System.Collections.Hashtable hints) + { + System.Collections.ArrayList possibleFormats = hints == null ? null : (System.Collections.ArrayList) hints[DecodeHintType.POSSIBLE_FORMATS]; + readers = new System.Collections.ArrayList(); + if (possibleFormats != null) { + if (possibleFormats.Contains(BarcodeFormat.EAN_13) || + possibleFormats.Contains(BarcodeFormat.UPC_A) || + possibleFormats.Contains(BarcodeFormat.EAN_8) || + possibleFormats.Contains(BarcodeFormat.UPC_E)) + { + readers.Add(new MultiFormatUPCEANReader(hints)); + } + if (possibleFormats.Contains(BarcodeFormat.CODE_39)) { + readers.Add(new Code39Reader()); + } + if (possibleFormats.Contains(BarcodeFormat.CODE_128)) + { + readers.Add(new Code128Reader()); + } + if (possibleFormats.Contains(BarcodeFormat.ITF)) + { + readers.Add(new ITFReader()); + } + } + if (readers.Count==0) { + readers.Contains(new MultiFormatUPCEANReader(hints)); + readers.Contains(new Code39Reader()); + readers.Contains(new Code128Reader()); + // TODO: Add ITFReader once it is validated as production ready, and tested for performance. + //readers.addElement(new ITFReader()); + } + } + + public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) + { + int size = readers.Count; + for (int i = 0; i < size; i++) { + OneDReader reader = (OneDReader) readers[i]; + try { + return reader.decodeRow(rowNumber, row, hints); + } catch (ReaderException re) { + // continue + } + } + + throw new ReaderException(); + } + + } +} \ No newline at end of file diff --git a/csharp/oned/MultiFormatUPCEANReader.cs b/csharp/oned/MultiFormatUPCEANReader.cs new file mode 100755 index 000000000..98dbf795e --- /dev/null +++ b/csharp/oned/MultiFormatUPCEANReader.cs @@ -0,0 +1,82 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + /** + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ + using System.Text; + using com.google.zxing.common; + + public sealed class MultiFormatUPCEANReader : AbstractOneDReader + { + private System.Collections.ArrayList readers; + public MultiFormatUPCEANReader(System.Collections.Hashtable hints) { + System.Collections.ArrayList possibleFormats = hints == null ? null : (System.Collections.ArrayList) hints[DecodeHintType.POSSIBLE_FORMATS]; + readers = new System.Collections.ArrayList(); + if (possibleFormats != null) { + if (possibleFormats.Contains(BarcodeFormat.EAN_13)) { + readers.Add(new EAN13Reader()); + } else if (possibleFormats.Contains(BarcodeFormat.UPC_A)) { + readers.Add(new UPCAReader()); + } + if (possibleFormats.Contains(BarcodeFormat.EAN_8)) { + readers.Add(new EAN8Reader()); + } + if (possibleFormats.Contains(BarcodeFormat.UPC_E)) { + readers.Add(new UPCEReader()); + } + } + if (readers.Count==0) { + readers.Add(new EAN13Reader()); + // UPC-A is covered by EAN-13 + readers.Add(new EAN8Reader()); + readers.Add(new UPCEReader()); + } + } + + public override Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) { + // Compute this location once and reuse it on multiple implementations + int[] startGuardPattern = AbstractUPCEANReader.findStartGuardPattern(row); + int size = readers.Count; + for (int i = 0; i < size; i++) { + UPCEANReader reader = (UPCEANReader) readers[i]; + Result result; + try { + result = reader.decodeRow(rowNumber, row, startGuardPattern); + } catch (ReaderException re) { + continue; + } + // Special case: a 12-digit code encoded in UPC-A is identical to a "0" + // followed by those 12 digits encoded as EAN-13. Each will recognize such a code, + // UPC-A as a 12-digit string and EAN-13 as a 13-digit string starting with "0". + // Individually these are correct and their readers will both read such a code + // and correctly call it EAN-13, or UPC-A, respectively. + // + // In this case, if we've been looking for both types, we'd like to call it + // a UPC-A code. But for efficiency we only run the EAN-13 decoder to also read + // UPC-A. So we special case it here, and convert an EAN-13 result to a UPC-A + // result if appropriate. + if (result.getBarcodeFormat().Equals(BarcodeFormat.EAN_13) && result.getText()[0] == '0') { + return new Result(result.getText().Substring(1), null, result.getResultPoints(), BarcodeFormat.UPC_A); + } + return result; + } + + throw new ReaderException(); + } + + } +} \ No newline at end of file diff --git a/csharp/oned/OneDReader.cs b/csharp/oned/OneDReader.cs new file mode 100755 index 000000000..d7d0ae43d --- /dev/null +++ b/csharp/oned/OneDReader.cs @@ -0,0 +1,41 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + /** + *

{@link Reader}s which also implement this interface read one-dimensional barcode + * formats, and expose additional functionality that is specific to this type of barcode.

+ * + * @author Sean Owen + */ + + using com.google.zxing.common; + + public interface OneDReader :Reader + { + /** + *

Attempts to decode a one-dimensional barcode format given a single row of + * an image.

+ * + * @param rowNumber row number from top of the row + * @param row the black/white pixel data of the row + * @param hints decode hints + * @return {@link Result} containing encoded string and start/end of barcode + * @throws ReaderException if an error occurs or barcode cannot be found + */ + Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints); + + } + +} \ No newline at end of file diff --git a/csharp/oned/UPCAReader.cs b/csharp/oned/UPCAReader.cs new file mode 100755 index 000000000..257e4c2de --- /dev/null +++ b/csharp/oned/UPCAReader.cs @@ -0,0 +1,54 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + /** + * @author dswitkin@google.com (Daniel Switkin) + * @author Sean Owen + */ + using System.Text; + using com.google.zxing.common; + + public sealed class UPCAReader : UPCEANReader + { + private UPCEANReader ean13Reader = new EAN13Reader(); + + public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) { + return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange)); + } + + public Result decodeRow(int rowNumber, BitArray row, System.Collections.Hashtable hints) { + return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, hints)); + } + + public Result decode(MonochromeBitmapSource image) { + return maybeReturnResult(ean13Reader.decode(image)); + } + + public Result decode(MonochromeBitmapSource image, System.Collections.Hashtable hints) { + return maybeReturnResult(ean13Reader.decode(image, hints)); + } + + private static Result maybeReturnResult(Result result) { + string text = result.getText(); + if (text[0] == '0') { + return new Result(text.Substring(1), null, result.getResultPoints(), BarcodeFormat.UPC_A); + } else { + throw new ReaderException(); + } + } + + + } +} \ No newline at end of file diff --git a/csharp/oned/UPCEANReader.cs b/csharp/oned/UPCEANReader.cs new file mode 100755 index 000000000..c4af8349c --- /dev/null +++ b/csharp/oned/UPCEANReader.cs @@ -0,0 +1,34 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using com.google.zxing.common; + /** + *

This interfaces captures addtional functionality that readers of + * UPC/EAN family of barcodes should expose.

+ * + * @author Sean Owen + */ + + public interface UPCEANReader : OneDReader + { + /** + *

Like {@link #decodeRow(int, BitArray, java.util.Hashtable)}, but + * allows caller to inform method about where the UPC/EAN start pattern is + * found. This allows this to be computed once and reused across many implementations.

+ */ + Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange); + } + +} \ No newline at end of file diff --git a/csharp/oned/UPCEReader.cs b/csharp/oned/UPCEReader.cs new file mode 100755 index 000000000..bed8653dd --- /dev/null +++ b/csharp/oned/UPCEReader.cs @@ -0,0 +1,151 @@ +/* +* 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. +*/ +namespace com.google.zxing.oned +{ + using System.Text; + using com.google.zxing.common; + /** + *

Implements decoding of the UPC-E format.

+ *

+ *

This is a great reference for + * UPC-E information.

+ * + * @author Sean Owen + */ + + + public sealed class UPCEReader : AbstractUPCEANReader + { + /** + * The pattern that marks the middle, and end, of a UPC-E pattern. + * There is no "second half" to a UPC-E barcode. + */ + private int[] MIDDLE_END_PATTERN = new int[]{1, 1, 1, 1, 1, 1}; + + /** + * See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of + * even-odd parity encodings of digits that imply both the number system (0 or 1) + * used, and the check digit. + */ + private static int[][] NUMSYS_AND_CHECK_DIGIT_PATTERNS = new int[][]{ + new int[]{0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25}, + new int[]{0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A} + }; + + private int[] decodeMiddleCounters; + + public UPCEReader() { + decodeMiddleCounters = new int[4]; + } + + protected override int decodeMiddle(BitArray row, int[] startRange, StringBuilder result) { + int[] counters = decodeMiddleCounters; + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = 0; + int end = row.getSize(); + int rowOffset = startRange[1]; + + int lgPatternFound = 0; + + for (int x = 0; x < 6 && rowOffset < end; x++) { + int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS); + result.Append((char) ('0' + bestMatch % 10)); + for (int i = 0; i < counters.Length; i++) { + rowOffset += counters[i]; + } + if (bestMatch >= 10) { + lgPatternFound |= 1 << (5 - x); + } + } + + determineNumSysAndCheckDigit(result, lgPatternFound); + + return rowOffset; + } + + public int[] decodeEnd(BitArray row, int endStart) { + return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN); + } + + public bool checkChecksum(string s) { + return base.checkChecksum(convertUPCEtoUPCA(s)); + } + + private static void determineNumSysAndCheckDigit(StringBuilder resultString, int lgPatternFound) + { + + for (int numSys = 0; numSys <= 1; numSys++) { + for (int d = 0; d < 10; d++) { + if (lgPatternFound == NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) { + resultString.Insert(0, (char) ('0' + numSys)); + resultString.Append((char) ('0' + d)); + return; + } + } + } + throw new ReaderException(); + } + + public override BarcodeFormat getBarcodeFormat() { + return BarcodeFormat.UPC_E; + } + + /** + * Expands a UPC-E value back into its full, equivalent UPC-A code value. + * + * @param upce UPC-E code as string of digits + * @return equivalent UPC-A code as string of digits + */ + public static string convertUPCEtoUPCA(string upce) { + char[] upceChars = new char[6]; + SupportClass.GetCharsFromString(upce, 1, 7, upceChars, 0); + StringBuilder result = new StringBuilder(12); + result.Append(upce[0]); + char lastChar = upceChars[5]; + switch (lastChar) { + case '0': + case '1': + case '2': + result.Append(upceChars, 0, 2); + result.Append(lastChar); + result.Append("0000"); + result.Append(upceChars, 2, 3); + break; + case '3': + result.Append(upceChars, 0, 3); + result.Append("00000"); + result.Append(upceChars, 3, 2); + break; + case '4': + result.Append(upceChars, 0, 4); + result.Append("00000"); + result.Append(upceChars[4]); + break; + default: + result.Append(upceChars, 0, 5); + result.Append("0000"); + result.Append(lastChar); + break; + } + result.Append(upce[7]); + return result.ToString(); + } + + + } + + +} diff --git a/csharp/qrcode/QRCodeReader.cs b/csharp/qrcode/QRCodeReader.cs new file mode 100755 index 000000000..b0b7c268d --- /dev/null +++ b/csharp/qrcode/QRCodeReader.cs @@ -0,0 +1,145 @@ +/* +* 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. +*/ +namespace com.google.zxing.qrcode +{ + using System; + using System.Collections; + using com.google.zxing.common; + using com.google.zxing.qrcode.decoder; + using com.google.zxing.qrcode.detector; + + public sealed class QRCodeReader: Reader + { + private static ResultPoint[] NO_POINTS = new ResultPoint[0]; + private Decoder decoder = new Decoder(); + + /** + * Locates and decodes a QR code in an image. + * + * @return a String representing the content encoded by the QR code + * @throws ReaderException if a QR code cannot be found, or cannot be decoded + */ + public Result decode(MonochromeBitmapSource image) { + try{ + return decode(image, null); + } + catch(Exception e){ + throw new ReaderException(e.Message); + } + + } + + public Result decode(MonochromeBitmapSource image, Hashtable hints){ + try{ + 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(hints); + decoderResult = decoder.decode(detectorResult.getBits()); + points = detectorResult.getPoints(); + } + + Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE); + if (decoderResult.getByteSegments() != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments()); + } + return result; + }catch(Exception e){ + throw new ReaderException(e.Message); + } + + } + + /** + * This method detects a barcode in a "pure" image -- that is, pure monochrome image + * which contains only an unrotated, unskewed, image of a barcode, 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; + while (moduleEnd < minDimension && image.isBlack(moduleEnd, moduleEnd)) { + moduleEnd++; + } + if (moduleEnd == minDimension) { + throw new ReaderException(); + } + + int moduleSize = moduleEnd - borderWidth; + + // And now find where the rightmost black module on the first row ends + int rowEndOfSymbol = width - 1; + while (rowEndOfSymbol >= 0 && !image.isBlack(rowEndOfSymbol, borderWidth)) { + rowEndOfSymbol--; + } + if (rowEndOfSymbol < 0) { + throw new ReaderException(); + } + rowEndOfSymbol++; + + // Make sure width of barcode is a multiple of module size + if ((rowEndOfSymbol - borderWidth) % moduleSize != 0) { + throw new ReaderException(); + } + int dimension = (rowEndOfSymbol - 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; + } + + + } + +} \ No newline at end of file diff --git a/csharp/qrcode/QRCodeWriter.cs b/csharp/qrcode/QRCodeWriter.cs new file mode 100755 index 000000000..9b684cf94 --- /dev/null +++ b/csharp/qrcode/QRCodeWriter.cs @@ -0,0 +1,143 @@ +/* +* 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. +*/ +namespace com.google.zxing.qrcode +{ + using System; + using System.Collections; + using com.google.zxing.common; + using com.google.zxing.qrcode.decoder; + using com.google.zxing.qrcode.detector; + using com.google.zxing.qrcode.encoder; + + public sealed class QRCodeWriter : Writer + { + private static int QUIET_ZONE_SIZE = 4; + public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height) + { + try{ + return encode(contents, format, width, height, null); + }catch(Exception e){ + throw new WriterException(e.Message); + } + } + + public ByteMatrix encode(String contents, BarcodeFormat format, int width, int height,Hashtable hints) { + + if (contents == null || contents.Length == 0) { + throw new ArgumentException("Found empty contents"); + } + + if (format != BarcodeFormat.QR_CODE) { + throw new ArgumentException("Can only encode QR_CODE, but got " + format); + } + + if (width < 0 || height < 0) { + throw new ArgumentException("Requested dimensions are too small: " + width + 'x' + + height); + } + + ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; + if (hints != null) { + ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints[EncodeHintType.ERROR_CORRECTION]; + if (requestedECLevel != null) { + errorCorrectionLevel = requestedECLevel; + } + } + + QRCode code = new QRCode(); + Encoder.encode(contents, errorCorrectionLevel, code); + return renderResult(code, width, height); + } + + // Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses + // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). + private static ByteMatrix renderResult(QRCode code, int width, int height) { + ByteMatrix input = code.getMatrix(); + int inputWidth = input.width(); + int inputHeight = input.height(); + int qrWidth = inputWidth + (QUIET_ZONE_SIZE << 1); + int qrHeight = inputHeight + (QUIET_ZONE_SIZE << 1); + int outputWidth = Math.Max(width, qrWidth); + int outputHeight = Math.Max(height, qrHeight); + + int multiple = Math.Min(outputWidth / qrWidth, outputHeight / qrHeight); + // Padding includes both the quiet zone and the extra white pixels to accomodate the requested + // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone. + // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will + // handle all the padding from 100x100 (the actual QR) up to 200x160. + int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; + int topPadding = (outputHeight - (inputHeight * multiple)) / 2; + + ByteMatrix output = new ByteMatrix(outputHeight, outputWidth); + sbyte[][] outputArray = output.getArray(); + + // We could be tricky and use the first row in each set of multiple as the temporary storage, + // instead of allocating this separate array. + sbyte[] row = new sbyte[outputWidth]; + + // 1. Write the white lines at the top + for (int y = 0; y < topPadding; y++) { + setRowColor(outputArray[y], unchecked((sbyte)255)); + } + + // 2. Expand the QR image to the multiple + sbyte[][] inputArray = input.getArray(); + for (int y = 0; y < inputHeight; y++) { + // a. Write the white pixels at the left of each row + for (int x = 0; x < leftPadding; x++) { + row[x] = unchecked((sbyte) 255); + } + + // b. Write the contents of this row of the barcode + int offset = leftPadding; + for (int x = 0; x < inputWidth; x++) { + sbyte value = (inputArray[y][x] == 1) ? (sbyte) 0 : unchecked((sbyte) 255); + for (int z = 0; z < multiple; z++) { + row[offset + z] = value; + } + offset += multiple; + } + + // c. Write the white pixels at the right of each row + offset = leftPadding + (inputWidth * multiple); + for (int x = offset; x < outputWidth; x++) { + row[x] = unchecked((sbyte) 255); + } + + // d. Write the completed row multiple times + offset = topPadding + (y * multiple); + for (int z = 0; z < multiple; z++) { + System.Array.Copy(row, 0, outputArray[offset + z], 0, outputWidth); + } + } + + // 3. Write the white lines at the bottom + int offset2 = topPadding + (inputHeight * multiple); + for (int y = offset2; y < outputHeight; y++) + { + setRowColor(outputArray[y], unchecked((sbyte) 255)); + } + return output; + } + + private static void setRowColor(sbyte[] row, sbyte value) { + for (int x = 0; x < row.Length; x++) { + row[x] = value; + } + } + + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/BitMatrixParser.cs b/csharp/qrcode/decoder/BitMatrixParser.cs new file mode 100755 index 000000000..7a2b643a6 --- /dev/null +++ b/csharp/qrcode/decoder/BitMatrixParser.cs @@ -0,0 +1,208 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.decoder +{ + public sealed class BitMatrixParser + { + private BitMatrix bitMatrix; + private Version parsedVersion; + private FormatInformation parsedFormatInfo; + + /** + * @param bitMatrix {@link BitMatrix} to parse + * @throws ReaderException if dimension is not >= 21 and 1 mod 4 + */ + public BitMatrixParser(BitMatrix bitMatrix){ + int dimension = bitMatrix.getDimension(); + if (dimension < 21 || (dimension & 0x03) != 1) { + throw new ReaderException(); + } + this.bitMatrix = bitMatrix; + } + + /** + *

Reads format information from one of its two locations within the QR Code.

+ * + * @return {@link FormatInformation} encapsulating the QR Code's format info + * @throws ReaderException if both format information locations cannot be parsed as + * the valid encoding of format information + */ + public FormatInformation readFormatInformation(){ + + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } + + // Read top-left format info bits + int formatInfoBits = 0; + for (int j = 0; j < 6; j++) { + formatInfoBits = copyBit(8, j, formatInfoBits); + } + // .. and skip a bit in the timing pattern ... + formatInfoBits = copyBit(8, 7, formatInfoBits); + formatInfoBits = copyBit(8, 8, formatInfoBits); + formatInfoBits = copyBit(7, 8, formatInfoBits); + // .. and skip a bit in the timing pattern ... + for (int i = 5; i >= 0; i--) { + formatInfoBits = copyBit(i, 8, formatInfoBits); + } + + parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits); + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } + + // Hmm, failed. Try the top-right/bottom-left pattern + int dimension = bitMatrix.getDimension(); + formatInfoBits = 0; + int iMin = dimension - 8; + for (int i = dimension - 1; i >= iMin; i--) { + formatInfoBits = copyBit(i, 8, formatInfoBits); + } + for (int j = dimension - 7; j < dimension; j++) { + formatInfoBits = copyBit(8, j, formatInfoBits); + } + + parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits); + if (parsedFormatInfo != null) { + return parsedFormatInfo; + } + throw new ReaderException(); + } + + /** + *

Reads version information from one of its two locations within the QR Code.

+ * + * @return {@link Version} encapsulating the QR Code's version + * @throws ReaderException if both version information locations cannot be parsed as + * the valid encoding of version information + */ + public Version readVersion(){ + + if (parsedVersion != null) { + return parsedVersion; + } + + int dimension = bitMatrix.getDimension(); + + int provisionalVersion = (dimension - 17) >> 2; + if (provisionalVersion <= 6) { + return Version.getVersionForNumber(provisionalVersion); + } + + // Read top-right version info: 3 wide by 6 tall + int versionBits = 0; + for (int i = 5; i >= 0; i--) { + int jMin = dimension - 11; + for (int j = dimension - 9; j >= jMin; j--) { + versionBits = copyBit(i, j, versionBits); + } + } + + parsedVersion = Version.decodeVersionInformation(versionBits); + if (parsedVersion != null) { + return parsedVersion; + } + + // Hmm, failed. Try bottom left: 6 wide by 3 tall + versionBits = 0; + for (int j = 5; j >= 0; j--) { + int iMin = dimension - 11; + for (int i = dimension - 11; i >= iMin; i--) { + versionBits = copyBit(i, j, versionBits); + } + } + + parsedVersion = Version.decodeVersionInformation(versionBits); + if (parsedVersion != null) { + return parsedVersion; + } + throw new ReaderException(); + } + + private int copyBit(int i, int j, int versionBits) { + return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1; + } + + /** + *

Reads the bits in the {@link BitMatrix} representing the finder pattern in the + * correct order in order to reconstitute the codewords bytes contained within the + * QR Code.

+ * + * @return bytes encoded within the QR Code + * @throws ReaderException if the exact number of bytes expected is not read + */ + public sbyte[] readCodewords(){ + + FormatInformation formatInfo = readFormatInformation(); + Version version = readVersion(); + + // Get the data mask for the format used in this QR Code. This will exclude + // some bits from reading as we wind through the bit matrix. + DataMask dataMask = DataMask.forReference((int) formatInfo.getDataMask()); + int dimension = bitMatrix.getDimension(); + dataMask.unmaskBitMatrix(bitMatrix.getBits(), dimension); + + BitMatrix functionPattern = version.buildFunctionPattern(); + + bool readingUp = true; + sbyte[] result = new sbyte[version.getTotalCodewords()]; + int resultOffset = 0; + int currentByte = 0; + int bitsRead = 0; + // Read columns in pairs, from right to left + for (int j = dimension - 1; j > 0; j -= 2) { + if (j == 6) { + // Skip whole column with vertical alignment pattern; + // saves time and makes the other code proceed more cleanly + j--; + } + // Read alternatingly from bottom to top then top to bottom + for (int count = 0; count < dimension; count++) { + int i = readingUp ? dimension - 1 - count : count; + for (int col = 0; col < 2; col++) { + // Ignore bits covered by the function pattern + if (!functionPattern.get(i, j - col)) { + // Read a bit + bitsRead++; + currentByte <<= 1; + if (bitMatrix.get(i, j - col)) { + currentByte |= 1; + } + // If we've made a whole byte, save it off + if (bitsRead == 8) { + result[resultOffset++] = (sbyte) currentByte; + bitsRead = 0; + currentByte = 0; + } + } + } + } + readingUp = !readingUp; // switch directions + } + if (resultOffset != version.getTotalCodewords()) { + throw new ReaderException(); + } + return result; + } + + + } + +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/DataBlock.cs b/csharp/qrcode/decoder/DataBlock.cs new file mode 100755 index 000000000..7fc4f5ff1 --- /dev/null +++ b/csharp/qrcode/decoder/DataBlock.cs @@ -0,0 +1,152 @@ +/* +* 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; +namespace com.google.zxing.qrcode.decoder +{ + + ///

Encapsulates a block of data within a QR Code. QR 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.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + public sealed class DataBlock + { + internal int NumDataCodewords + { + get + { + return numDataCodewords; + } + + } + internal sbyte[] Codewords + { + get + { + return codewords; + } + + } + + //UPGRADE_NOTE: Final was removed from the declaration of 'numDataCodewords '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private int numDataCodewords; + //UPGRADE_NOTE: Final was removed from the declaration of 'codewords '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private sbyte[] codewords; + + public DataBlock(int numDataCodewords, sbyte[] codewords) + { + this.numDataCodewords = numDataCodewords; + this.codewords = codewords; + } + + ///

When QR Codes use multiple data blocks, they are 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.

+ /// + ///
+ /// bytes as read directly from the QR Code + /// + /// version of the QR Code + /// + /// error-correction level of the QR Code + /// + /// {@link DataBlock}s containing original bytes, "de-interleaved" from representation in the + /// QR Code + /// + internal static DataBlock[] getDataBlocks(sbyte[] rawCodewords, Version version, ErrorCorrectionLevel ecLevel) + { + // Figure out the number and size of data blocks used by this version and + // error correction level + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + + // 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.getTotalECCodewords() + 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 more byte. Figure out where these start. + int shorterBlocksTotalCodewords = result[0].codewords.Length; + int longerBlocksStartAt = result.Length - 1; + while (longerBlocksStartAt >= 0) + { + int numCodewords = result[longerBlocksStartAt].codewords.Length; + if (numCodewords == shorterBlocksTotalCodewords) + { + break; + } + if (numCodewords != shorterBlocksTotalCodewords + 1) + { + throw new System.SystemException("Data block sizes differ by more than 1"); + } + longerBlocksStartAt--; + } + longerBlocksStartAt++; + + int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getTotalECCodewords(); + // The last elements of result may be 1 element longer; + // first fill out as many elements as all of them have + 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 + for (int j = longerBlocksStartAt; j < numResultBlocks; j++) + { + result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; + } + // Now add in error correction blocks + int max = result[0].codewords.Length; + for (int i = shorterBlocksNumDataCodewords; i < max; i++) + { + for (int j = 0; j < numResultBlocks; j++) + { + int iOffset = j < longerBlocksStartAt ? i : i + 1; + result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; + } + } + + if (rawCodewordsOffset != rawCodewords.Length) + { + throw new System.SystemException(); + } + + return result; + } + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/DataMask.cs b/csharp/qrcode/decoder/DataMask.cs new file mode 100755 index 000000000..be2507905 --- /dev/null +++ b/csharp/qrcode/decoder/DataMask.cs @@ -0,0 +1,282 @@ +/* +* 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; +namespace com.google.zxing.qrcode.decoder +{ + + ///

Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations + /// of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix, + /// including areas used for finder patterns, timing patterns, etc. These areas should be unused + /// after the point they are unmasked anyway.

+ /// + ///

Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position + /// and j is row position. In fact, as the text says, i is row position and j is column position.

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + abstract class DataMask + { + + /// See ISO 18004:2006 6.8.1 + //UPGRADE_NOTE: Final was removed from the declaration of 'DATA_MASKS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private static readonly DataMask[] DATA_MASKS = new DataMask[] { new DataMask000(), new DataMask001(), new DataMask010(), new DataMask011(), new DataMask100(), new DataMask101(), new DataMask110(), new DataMask111() }; + + private DataMask() + { + } + + ///

Implementations of this method reverse the data masking process applied to a QR Code and + /// make its bits ready to read.

+ /// + ///
+ /// representation of QR Code bits from {@link com.google.zxing.common.BitMatrix#getBits()} + /// + /// dimension of QR Code, represented by bits, being unmasked + /// + internal abstract void unmaskBitMatrix(int[] bits, int dimension); + + /// a value between 0 and 7 indicating one of the eight possible + /// data mask patterns a QR Code may use + /// + /// {@link DataMask} encapsulating the data mask pattern + /// + internal static DataMask forReference(int reference) + { + if (reference < 0 || reference > 7) + { + throw new System.ArgumentException(); + } + return DATA_MASKS[reference]; + } + + /// 000: mask bits for which (i + j) mod 2 == 0 + private class DataMask000 : DataMask + { + private const int BITMASK = 0x55555555; // = 010101... + + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + // This one's easy. Because the dimension of BitMatrix is always odd, + // we can merely flip every other bit + int max = bits.Length; + for (int i = 0; i < max; i++) + { + bits[i] ^= BITMASK; + } + } + } + + /// 001: mask bits for which i mod 2 == 0 + private class DataMask001 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + for (int i = 0; i < dimension; i++) + { + if ((i & 0x01) == 0) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + + /// 010: mask bits for which j mod 3 == 0 + private class DataMask010 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + bool columnMasked = j % 3 == 0; + for (int i = 0; i < dimension; i++) + { + if (columnMasked) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + + /// 011: mask bits for which (i + j) mod 3 == 0 + private class DataMask011 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + for (int i = 0; i < dimension; i++) + { + if ((i + j) % 3 == 0) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + + /// 100: mask bits for which (i/2 + j/3) mod 2 == 0 + private class DataMask100 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + int jComponentParity = (j / 3) & 0x01; + for (int i = 0; i < dimension; i++) + { + if (((i >> 1) & 0x01) == jComponentParity) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + + /// 101: mask bits for which ij mod 2 + ij mod 3 == 0 + private class DataMask101 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + for (int i = 0; i < dimension; i++) + { + int product = i * j; + if (((product & 0x01) == 0) && product % 3 == 0) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + + /// 110: mask bits for which (ij mod 2 + ij mod 3) mod 2 == 0 + private class DataMask110 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + for (int i = 0; i < dimension; i++) + { + int product = i * j; + if ((((product & 0x01) + product % 3) & 0x01) == 0) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + + /// 111: mask bits for which ((i+j)mod 2 + ij mod 3) mod 2 == 0 + private class DataMask111 : DataMask + { + internal override void unmaskBitMatrix(int[] bits, int dimension) + { + int bitMask = 0; + int count = 0; + int offset = 0; + for (int j = 0; j < dimension; j++) + { + for (int i = 0; i < dimension; i++) + { + if (((((i + j) & 0x01) + (i * j) % 3) & 0x01) == 0) + { + bitMask |= 1 << count; + } + if (++count == 32) + { + bits[offset++] ^= bitMask; + count = 0; + bitMask = 0; + } + } + } + bits[offset] ^= bitMask; + } + } + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/DecodedBitStreamParser.cs b/csharp/qrcode/decoder/DecodedBitStreamParser.cs new file mode 100755 index 000000000..dfe82fa1e --- /dev/null +++ b/csharp/qrcode/decoder/DecodedBitStreamParser.cs @@ -0,0 +1,266 @@ +/* +* 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 ReaderException = com.google.zxing.ReaderException; +using com.google.zxing.common; +namespace com.google.zxing.qrcode.decoder +{ + + ///

QR Codes can encode text as bits in one of several modes, and can use multiple modes + /// in one QR Code. This class decodes the bits back into text.

+ /// + ///

See ISO 18004:2006, 6.4.3 - 6.4.7

+ /// + ///
+ /// srowen@google.com (Sean Owen) + /// + public sealed class DecodedBitStreamParser + { + + /// See ISO 18004:2006, 6.4.4 Table 5 + //UPGRADE_NOTE: Final was removed from the declaration of 'ALPHANUMERIC_CHARS '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" + private static readonly char[] ALPHANUMERIC_CHARS = new char[] { '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 const System.String SHIFT_JIS = "Shift_JIS"; + private static bool ASSUME_SHIFT_JIS; + + private DecodedBitStreamParser() + { + } + + internal static System.String decode(sbyte[] bytes, Version version) + { + BitSource bits = new BitSource(bytes); + System.Text.StringBuilder result = new System.Text.StringBuilder(); + Mode mode; + do + { + // While still another segment to read... + mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits + if (!mode.Equals(Mode.TERMINATOR)) + { + // How many characters will follow, encoded in this mode? + int count = bits.readBits(mode.getCharacterCountBits(version)); + if (mode.Equals(Mode.NUMERIC)) + { + decodeNumericSegment(bits, result, count); + } + else if (mode.Equals(Mode.ALPHANUMERIC)) + { + decodeAlphanumericSegment(bits, result, count); + } + else if (mode.Equals(Mode.BYTE)) + { + decodeByteSegment(bits, result, count); + } + else if (mode.Equals(Mode.KANJI)) + { + decodeKanjiSegment(bits, result, count); + } + else + { + throw new ReaderException("Unsupported mode indicator"); + } + } + } + while (!mode.Equals(Mode.TERMINATOR)); + + // I thought it wasn't allowed to leave extra bytes after the terminator but it happens + /* + int bitsLeft = bits.available(); + if (bitsLeft > 0) { + if (bitsLeft > 6 || bits.readBits(bitsLeft) != 0) { + throw new ReaderException("Excess bits or non-zero bits after terminator mode indicator"); + } + } + */ + return result.ToString(); + } + + private static void decodeKanjiSegment(BitSource bits, System.Text.StringBuilder result, int count) + { + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as Shift_JIS afterwards + sbyte[] buffer = new sbyte[2 * count]; + int offset = 0; + while (count > 0) + { + // Each 13 bits encodes a 2-byte character + int twoBytes = bits.readBits(13); + int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); + if (assembledTwoBytes < 0x01F00) + { + // In the 0x8140 to 0x9FFC range + assembledTwoBytes += 0x08140; + } + else + { + // In the 0xE040 to 0xEBBF range + assembledTwoBytes += 0x0C140; + } + buffer[offset] = (sbyte)(assembledTwoBytes >> 8); + buffer[offset + 1] = (sbyte)assembledTwoBytes; + offset += 2; + count--; + } + // Shift_JIS may not be supported in some environments: + try + { + byte[] bytes = SupportClass.ToByteArray(buffer); + //UPGRADE_TODO: The differences in the Format of parameters for constructor 'java.lang.String.String' may cause compilation errors. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1092'" + result.Append(System.Text.Encoding.GetEncoding("Shift_JIS").GetString(bytes, 0, bytes.Length)); + } + catch (System.IO.IOException uee) + { + throw new ReaderException("SHIFT_JIS encoding is not supported on this device"); + } + } + + private static void decodeByteSegment(BitSource bits, System.Text.StringBuilder result, int count) + { + sbyte[] readBytes = new sbyte[count]; + if (count << 3 > bits.available()) + { + throw new ReaderException("Count too large: " + count); + } + for (int i = 0; i < count; i++) + { + readBytes[i] = (sbyte)bits.readBits(8); + } + // The spec isn't clear on this mode; see + // section 6.4.5: t does not say which encoding to assuming + // upon decoding. I have seen ISO-8859-1 used as well as + // Shift_JIS -- without anything like an ECI designator to + // give a hint. + System.String encoding = guessEncoding(readBytes); + try + { + byte[] bytes = SupportClass.ToByteArray(readBytes); + //System.Windows.Forms.MessageBox.Show("encodings: "+ System.Text.Encoding.()); + //UPGRADE_TODO: The differences in the Format of parameters for constructor 'java.lang.String.String' may cause compilation errors. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1092'" + result.Append(System.Text.Encoding.GetEncoding(encoding).GetString(bytes, 0, bytes.Length)); + } + catch (System.IO.IOException uce) + { + //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'" + throw new ReaderException(uce.ToString()); + } + } + + private static void decodeAlphanumericSegment(BitSource bits, System.Text.StringBuilder result, int count) + { + // Read two characters at a time + while (count > 1) + { + int nextTwoCharsBits = bits.readBits(11); + result.Append(ALPHANUMERIC_CHARS[nextTwoCharsBits / 45]); + result.Append(ALPHANUMERIC_CHARS[nextTwoCharsBits % 45]); + count -= 2; + } + if (count == 1) + { + // special case: one character left + result.Append(ALPHANUMERIC_CHARS[bits.readBits(6)]); + } + } + + private static void decodeNumericSegment(BitSource bits, System.Text.StringBuilder result, int count) + { + // Read three digits at a time + while (count >= 3) + { + // Each 10 bits encodes three digits + int threeDigitsBits = bits.readBits(10); + if (threeDigitsBits >= 1000) + { + throw new ReaderException("Illegal value for 3-digit unit: " + threeDigitsBits); + } + result.Append(ALPHANUMERIC_CHARS[threeDigitsBits / 100]); + result.Append(ALPHANUMERIC_CHARS[(threeDigitsBits / 10) % 10]); + result.Append(ALPHANUMERIC_CHARS[threeDigitsBits % 10]); + count -= 3; + } + if (count == 2) + { + // Two digits left over to read, encoded in 7 bits + int twoDigitsBits = bits.readBits(7); + if (twoDigitsBits >= 100) + { + throw new ReaderException("Illegal value for 2-digit unit: " + twoDigitsBits); + } + result.Append(ALPHANUMERIC_CHARS[twoDigitsBits / 10]); + result.Append(ALPHANUMERIC_CHARS[twoDigitsBits % 10]); + } + else if (count == 1) + { + // One digit left over to read + int digitBits = bits.readBits(4); + if (digitBits >= 10) + { + throw new ReaderException("Illegal value for digit unit: " + digitBits); + } + result.Append(ALPHANUMERIC_CHARS[digitBits]); + } + } + + private static System.String guessEncoding(sbyte[] bytes) + { + if (ASSUME_SHIFT_JIS) + { + return SHIFT_JIS; + } + // For now, merely tries to distinguish ISO-8859-1 and Shift_JIS, + // which should be by far the most common encodings. ISO-8859-1 + // should not have bytes in the 0x80 - 0x9F range, while Shift_JIS + // uses this as a first byte of a two-byte character. If we see this + // followed by a valid second byte in Shift_JIS, assume it is Shift_JIS. + int length = bytes.Length; + for (int i = 0; i < length; i++) + { + int value_Renamed = bytes[i] & 0xFF; + if (value_Renamed >= 0x80 && value_Renamed <= 0x9F && i < length - 1) + { + // ISO-8859-1 shouldn't use this, but before we decide it is Shift_JIS, + // just double check that it is followed by a byte that's valid in + // the Shift_JIS encoding + int nextValue = bytes[i + 1] & 0xFF; + if ((value_Renamed & 0x1) == 0) + { + // if even, + if (nextValue >= 0x40 && nextValue <= 0x9E) + { + return SHIFT_JIS; + } + } + else + { + if (nextValue >= 0x9F && nextValue <= 0x7C) + { + return SHIFT_JIS; + } + } + } + } + return "ASCII"; + } + //static DecodedBitStreamParser() + //{ + // { + // //UPGRADE_ISSUE: Method 'java.lang.System.getProperty' was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1000_javalangSystem'" + // System.String platformDefault = System_Renamed.getProperty("file.encoding"); + // ASSUME_SHIFT_JIS = SHIFT_JIS.ToUpper().Equals(platformDefault.ToUpper()) || "EUC-JP".ToUpper().Equals(platformDefault.ToUpper()); + // } + //} + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/Decoder.cs b/csharp/qrcode/decoder/Decoder.cs new file mode 100755 index 000000000..0e5993b37 --- /dev/null +++ b/csharp/qrcode/decoder/Decoder.cs @@ -0,0 +1,140 @@ +/* +* 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; +namespace com.google.zxing.qrcode.decoder +{ + using com.google.zxing.common; + using com.google.zxing.common.reedsolomon; + + public sealed class Decoder + { + private ReedSolomonDecoder rsDecoder; + + public Decoder() { + rsDecoder = new ReedSolomonDecoder(GF256.QR_CODE_FIELD); + } + + /** + *

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

+ * + * @param image booleans representing white/black QR Code modules + * @return text and bytes encoded within the QR Code + * @throws ReaderException if the QR Code cannot be decoded + */ + public DecoderResult decode(bool[][] image) { + try{ + 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); + }catch (Exception e){ + throw new ReaderException(e.Message); + } + } + + /** + *

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

+ * + * @param bits booleans representing white/black QR Code modules + * @return text and bytes encoded within the QR Code + * @throws ReaderException if the QR Code cannot be decoded + */ + public DecoderResult decode(BitMatrix bits){ + try{ + // Construct a parser and read version, error-correction level + BitMatrixParser parser = new BitMatrixParser(bits); + Version version = parser.readVersion(); + ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel(); + + // Read codewords + sbyte[] codewords = parser.readCodewords(); + // Separate into data blocks + DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); + + // Count total number of data bytes + int totalBytes = 0; + for (int i = 0; i < dataBlocks.Length; i++) { + totalBytes += dataBlocks[i].NumDataCodewords; + } + 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.Codewords; + int numDataCodewords = dataBlock.NumDataCodewords; + correctErrors(codewordBytes, numDataCodewords); + for (int i = 0; i < numDataCodewords; i++) { + resultBytes[resultOffset++] = codewordBytes[i]; + } + } + + // Decode the contents of that stream of bytes + string sResult = DecodedBitStreamParser.decode(resultBytes, version); + return new DecoderResult(resultBytes, sResult, null); + }catch(Exception e){ + throw new ReaderException(e.Message); + } + + } + + /** + *

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){ + try + { + 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(rse.Message); + } + // 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]; + } + } + catch (Exception e) { + throw new ReaderException(e.Message); + } + } + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/ErrorCorrectionLevel.cs b/csharp/qrcode/decoder/ErrorCorrectionLevel.cs new file mode 100755 index 000000000..c16e62fc6 --- /dev/null +++ b/csharp/qrcode/decoder/ErrorCorrectionLevel.cs @@ -0,0 +1,81 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.decoder +{ + public sealed class ErrorCorrectionLevel + { + // No, we can't use an enum here. J2ME doesn't support it. + /** + * L = ~7% correction + */ + public static ErrorCorrectionLevel L = new ErrorCorrectionLevel(0, 0x01, "L"); + /** + * M = ~15% correction + */ + public static ErrorCorrectionLevel M = new ErrorCorrectionLevel(1, 0x00, "M"); + /** + * Q = ~25% correction + */ + public static ErrorCorrectionLevel Q = new ErrorCorrectionLevel(2, 0x03, "Q"); + /** + * H = ~30% correction + */ + public static ErrorCorrectionLevel H = new ErrorCorrectionLevel(3, 0x02, "H"); + + private static ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q}; + + private int Ordinal; + private int bits; + private string name; + + private ErrorCorrectionLevel(int ordinal, int bits, string name) { + this.Ordinal = ordinal; + this.bits = bits; + this.name = name; + } + + public int ordinal() { + return Ordinal; + } + + public int getBits() { + return bits; + } + + public string getName() { + return name; + } + + public string toString() { + return name; + } + + /** + * @param bits int containing the two bits encoding a QR Code's error correction level + * @return {@link ErrorCorrectionLevel} representing the encoded error correction level + */ + public static ErrorCorrectionLevel forBits(int bits) { + if (bits < 0 || bits >= FOR_BITS.Length) { + throw new ArgumentException(); + } + return FOR_BITS[bits]; + } + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/FormatInformation.cs b/csharp/qrcode/decoder/FormatInformation.cs new file mode 100755 index 000000000..07aa37de5 --- /dev/null +++ b/csharp/qrcode/decoder/FormatInformation.cs @@ -0,0 +1,118 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.decoder +{ + public sealed class FormatInformation + { + private static int FORMAT_INFO_MASK_QR = 0x5412; + /** + * See ISO 18004:2006, Annex C, Table C.1 + */ + private static readonly int[][] FORMAT_INFO_DECODE_LOOKUP = new int[][] { new int[] { 0x5412, 0x00 }, new int[] { 0x5125, 0x01 }, new int[] { 0x5E7C, 0x02 }, new int[] { 0x5B4B, 0x03 }, new int[] { 0x45F9, 0x04 }, new int[] { 0x40CE, 0x05 }, new int[] { 0x4F97, 0x06 }, new int[] { 0x4AA0, 0x07 }, new int[] { 0x77C4, 0x08 }, new int[] { 0x72F3, 0x09 }, new int[] { 0x7DAA, 0x0A }, new int[] { 0x789D, 0x0B }, new int[] { 0x662F, 0x0C }, new int[] { 0x6318, 0x0D }, new int[] { 0x6C41, 0x0E }, new int[] { 0x6976, 0x0F }, new int[] { 0x1689, 0x10 }, new int[] { 0x13BE, 0x11 }, new int[] { 0x1CE7, 0x12 }, new int[] { 0x19D0, 0x13 }, new int[] { 0x0762, 0x14 }, new int[] { 0x0255, 0x15 }, new int[] { 0x0D0C, 0x16 }, new int[] { 0x083B, 0x17 }, new int[] { 0x355F, 0x18 }, new int[] { 0x3068, 0x19 }, new int[] { 0x3F31, 0x1A }, new int[] { 0x3A06, 0x1B }, new int[] { 0x24B4, 0x1C }, new int[] { 0x2183, 0x1D }, new int[] { 0x2EDA, 0x1E }, new int[] { 0x2BED, 0x1F } }; + + /** + * Offset i holds the number of 1 bits in the binary representation of i + */ + private static int[] BITS_SET_IN_HALF_BYTE = + {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + private ErrorCorrectionLevel errorCorrectionLevel; + private byte dataMask; + + private FormatInformation(int formatInfo) { + // Bits 3,4 + errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); + // Bottom 3 bits + dataMask = (byte) (formatInfo & 0x07); + } + + public static int numBitsDiffering(int a, int b) { + a ^= b; // a now has a 1 bit exactly where its bit differs with b's + // Count bits set quickly with a series of lookups: + return BITS_SET_IN_HALF_BYTE[a & 0x0F] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 4) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 8) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 12) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 16) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 20) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 24) & 0x0F)] + BITS_SET_IN_HALF_BYTE[(SupportClass.URShift(a, 28) & 0x0F)]; + } + + /** + * @param rawFormatInfo + * @return + */ + public static FormatInformation decodeFormatInformation(int rawFormatInfo){ + try{ + FormatInformation formatInfo = doDecodeFormatInformation(rawFormatInfo); + if (formatInfo != null) { + return formatInfo; + } + // Should return null, but, some QR codes apparently + // do not mask this info. Try again, first masking the raw bits so + // the function will unmask + return doDecodeFormatInformation(rawFormatInfo ^ FORMAT_INFO_MASK_QR); + }catch(Exception e){ + throw new ReaderException(e.Message); + } + } + + private static FormatInformation doDecodeFormatInformation(int rawFormatInfo) { + // Unmask: + int unmaskedFormatInfo = rawFormatInfo ^ FORMAT_INFO_MASK_QR; + // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing + int bestDifference = int.MaxValue; + int bestFormatInfo = 0; + for (int i = 0; i < FORMAT_INFO_DECODE_LOOKUP.Length; i++) { + int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; + int targetInfo = decodeInfo[0]; + if (targetInfo == unmaskedFormatInfo) { + // Found an exact match + return new FormatInformation(decodeInfo[1]); + } + int bitsDifference = numBitsDiffering(unmaskedFormatInfo, targetInfo); + if (bitsDifference < bestDifference) { + bestFormatInfo = decodeInfo[1]; + bestDifference = bitsDifference; + } + } + if (bestDifference <= 3) { + return new FormatInformation(bestFormatInfo); + } + return null; + } + + public ErrorCorrectionLevel getErrorCorrectionLevel() { + return errorCorrectionLevel; + } + + public byte getDataMask() { + return dataMask; + } + + public int hashCode() { + return (errorCorrectionLevel.ordinal() << 3) | (int) dataMask; + } + + public bool equals(Object o) { + if (!(o.GetType() == typeof(FormatInformation))){ + return false; + } + FormatInformation other = (FormatInformation) o; + return this.errorCorrectionLevel == other.errorCorrectionLevel && + this.dataMask == other.dataMask; + } + + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/Mode.cs b/csharp/qrcode/decoder/Mode.cs new file mode 100755 index 000000000..f06f89030 --- /dev/null +++ b/csharp/qrcode/decoder/Mode.cs @@ -0,0 +1,108 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.decoder +{ + public sealed class Mode + { + // No, we can't use an enum here. J2ME doesn't support it. + public static Mode TERMINATOR = new Mode(new int[]{0, 0, 0}, 0x00, "TERMINATOR"); // Not really a mode... + public static Mode NUMERIC = new Mode(new int[]{10, 12, 14}, 0x01, "NUMERIC"); + public static Mode ALPHANUMERIC = new Mode(new int[]{9, 11, 13}, 0x02, "ALPHANUMERIC"); + public static Mode BYTE = new Mode(new int[]{8, 16, 16}, 0x04, "BYTE"); + public static Mode ECI = new Mode(null, 0x07, "ECI"); // character counts don't apply + public static Mode KANJI = new Mode(new int[]{8, 10, 12}, 0x08, "KANJI"); + public static Mode FNC1_FIRST_POSITION = new Mode(null, 0x05, "FNC1_FIRST_POSITION"); + public static Mode FNC1_SECOND_POSITION = new Mode(null, 0x09, "FNC1_SECOND_POSITION"); + + private int[] characterCountBitsForVersions; + private int bits; + private string name; + + private Mode(int[] characterCountBitsForVersions, int bits, string name) { + this.characterCountBitsForVersions = characterCountBitsForVersions; + this.bits = bits; + this.name = name; + } + + /** + * @param bits four bits encoding a QR Code data mode + * @return {@link Mode} encoded by these bits + * @throws ArgumentException if bits do not correspond to a known mode + */ + public static Mode forBits(int bits) { + switch (bits) { + case 0x0: + return TERMINATOR; + case 0x1: + return NUMERIC; + case 0x2: + return ALPHANUMERIC; + case 0x4: + return BYTE; + case 0x5: + return FNC1_FIRST_POSITION; + case 0x7: + return ECI; + case 0x8: + return KANJI; + case 0x9: + return FNC1_SECOND_POSITION; + default: + throw new ArgumentException(); + } + } + + /** + * @param version version in question + * @return number of bits used, in this QR Code symbol {@link Version}, to encode the + * count of characters that will follow encoded in this {@link Mode} + */ + public int getCharacterCountBits(Version version) { + if (characterCountBitsForVersions == null) { + throw new ArgumentException("Character count doesn't apply to this mode"); + } + int number = version.getVersionNumber(); + int offset; + if (number <= 9) { + offset = 0; + } else if (number <= 26) { + offset = 1; + } else { + offset = 2; + } + return characterCountBitsForVersions[offset]; + } + + public int getBits() { + return bits; + } + + public string getName() { + return name; + } + + public string toString() { + return name; + } + + + + } +} \ No newline at end of file diff --git a/csharp/qrcode/decoder/Version.cs b/csharp/qrcode/decoder/Version.cs new file mode 100755 index 000000000..502b80acc --- /dev/null +++ b/csharp/qrcode/decoder/Version.cs @@ -0,0 +1,579 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.decoder +{ + public sealed class Version + { + /** + * See ISO 18004:2006 Annex D. + * Element i represents the raw version bits that specify version i + 7 + */ + private static int[] VERSION_DECODE_INFO = { + 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6, + 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78, + 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683, + 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB, + 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250, + 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B, + 0x2542E, 0x26A64, 0x27541, 0x28C69 + }; + + private static Version[] VERSIONS = buildVersions(); + + private int versionNumber; + private int[] alignmentPatternCenters; + private ECBlocks[] ecBlocks; + private int totalCodewords; + + private Version(int versionNumber, + int[] alignmentPatternCenters, + ECBlocks ecBlocks1, + ECBlocks ecBlocks2, + ECBlocks ecBlocks3, + ECBlocks ecBlocks4) { + this.versionNumber = versionNumber; + this.alignmentPatternCenters = alignmentPatternCenters; + this.ecBlocks = new ECBlocks[]{ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4}; + int total = 0; + int ecCodewords = ecBlocks1.getECCodewordsPerBlock(); + ECB[] ecbArray = ecBlocks1.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[] getAlignmentPatternCenters() { + return alignmentPatternCenters; + } + + public int getTotalCodewords() { + return totalCodewords; + } + + public int getDimensionForVersion() { + return 17 + 4 * versionNumber; + } + + public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) { + return ecBlocks[ecLevel.ordinal()]; + } + + /** + *

Deduces version information purely from QR Code dimensions.

+ * + * @param dimension dimension in modules + * @return {@link Version} for a QR Code of that dimension + * @throws ReaderException if dimension is not 1 mod 4 + */ + public static Version getProvisionalVersionForDimension(int dimension) { + if (dimension % 4 != 1) { + throw new ReaderException(); + } + return getVersionForNumber((dimension - 17) >> 2); + } + + public static Version getVersionForNumber(int versionNumber) { + if (versionNumber < 1 || versionNumber > 40) { + throw new ArgumentException(); + } + return VERSIONS[versionNumber - 1]; + } + + public static Version decodeVersionInformation(int versionBits) { + int bestDifference = int.MaxValue; + int bestVersion = 0; + for (int i = 0; i < VERSION_DECODE_INFO.Length; i++) { + int targetVersion = VERSION_DECODE_INFO[i]; + // Do the version info bits match exactly? done. + if (targetVersion == versionBits) { + return getVersionForNumber(i + 7); + } + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion); + if (bitsDifference < bestDifference) { + bestVersion = i + 7; + } + } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 4 bits. + if (bestDifference <= 3) { + return getVersionForNumber(bestVersion); + } + // If we didn't find a close enough match, fail + return null; + } + + /** + * See ISO 18004:2006 Annex E + */ + public BitMatrix buildFunctionPattern() { + int dimension = getDimensionForVersion(); + BitMatrix bitMatrix = new BitMatrix(dimension); + + // Top left finder pattern + separator + format + bitMatrix.setRegion(0, 0, 9, 9); + // Top right finder pattern + separator + format + bitMatrix.setRegion(0, dimension - 8, 9, 8); + // Bottom left finder pattern + separator + format + bitMatrix.setRegion(dimension - 8, 0, 8, 9); + + // Alignment patterns + int max = alignmentPatternCenters.Length; + for (int x = 0; x < max; x++) { + int i = alignmentPatternCenters[x] - 2; + for (int y = 0; y < max; y++) { + if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) { + // No alignment patterns near the three finder paterns + continue; + } + bitMatrix.setRegion(i, alignmentPatternCenters[y] - 2, 5, 5); + } + } + + // Vertical timing pattern + bitMatrix.setRegion(9, 6, dimension - 17, 1); + // Horizontal timing pattern + bitMatrix.setRegion(6, 9, 1, dimension - 17); + + if (versionNumber > 6) { + // Version info, top right + bitMatrix.setRegion(0, dimension - 11, 6, 3); + // Version info, bottom left + bitMatrix.setRegion(dimension - 11, 0, 3, 6); + } + + return bitMatrix; + } + + /** + *

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 ecCodewordsPerBlock; + private ECB[] ecBlocks; + + public ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks) { + this.ecCodewordsPerBlock = ecCodewordsPerBlock; + this.ecBlocks = new ECB[]{ecBlocks}; + } + + public ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks1, ECB ecBlocks2) + { + this.ecCodewordsPerBlock = ecCodewordsPerBlock; + this.ecBlocks = new ECB[]{ecBlocks1, ecBlocks2}; + } + + public int getECCodewordsPerBlock() { + return ecCodewordsPerBlock; + } + + public int getNumBlocks() { + int total = 0; + for (int i = 0; i < ecBlocks.Length; i++) { + total += ecBlocks[i].getCount(); + } + return total; + } + + public int getTotalECCodewords() { + return ecCodewordsPerBlock * getNumBlocks(); + } + + 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 QR 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(); + //return String.valueOf(versionNumber); + } + + /** + * See ISO 18004:2006 6.5.1 Table 9 + */ + private static Version[] buildVersions() { + return new Version[]{ + new Version(1, new int[]{}, + new ECBlocks(7, new ECB(1, 19)), + new ECBlocks(10, new ECB(1, 16)), + new ECBlocks(13, new ECB(1, 13)), + new ECBlocks(17, new ECB(1, 9))), + new Version(2, new int[]{6, 18}, + new ECBlocks(10, new ECB(1, 34)), + new ECBlocks(16, new ECB(1, 28)), + new ECBlocks(22, new ECB(1, 22)), + new ECBlocks(28, new ECB(1, 16))), + new Version(3, new int[]{6, 22}, + new ECBlocks(15, new ECB(1, 55)), + new ECBlocks(26, new ECB(1, 44)), + new ECBlocks(18, new ECB(2, 17)), + new ECBlocks(22, new ECB(2, 13))), + new Version(4, new int[]{6, 26}, + new ECBlocks(20, new ECB(1, 80)), + new ECBlocks(18, new ECB(2, 32)), + new ECBlocks(26, new ECB(2, 24)), + new ECBlocks(16, new ECB(4, 9))), + new Version(5, new int[]{6, 30}, + new ECBlocks(26, new ECB(1, 108)), + new ECBlocks(24, new ECB(2, 43)), + new ECBlocks(18, new ECB(2, 15), + new ECB(2, 16)), + new ECBlocks(22, new ECB(2, 11), + new ECB(2, 12))), + new Version(6, new int[]{6, 34}, + new ECBlocks(18, new ECB(2, 68)), + new ECBlocks(16, new ECB(4, 27)), + new ECBlocks(24, new ECB(4, 19)), + new ECBlocks(28, new ECB(4, 15))), + new Version(7, new int[]{6, 22, 38}, + new ECBlocks(20, new ECB(2, 78)), + new ECBlocks(18, new ECB(4, 31)), + new ECBlocks(18, new ECB(2, 14), + new ECB(4, 15)), + new ECBlocks(26, new ECB(4, 13), + new ECB(1, 14))), + new Version(8, new int[]{6, 24, 42}, + new ECBlocks(24, new ECB(2, 97)), + new ECBlocks(22, new ECB(2, 38), + new ECB(2, 39)), + new ECBlocks(22, new ECB(4, 18), + new ECB(2, 19)), + new ECBlocks(26, new ECB(4, 14), + new ECB(2, 15))), + new Version(9, new int[]{6, 26, 46}, + new ECBlocks(30, new ECB(2, 116)), + new ECBlocks(22, new ECB(3, 36), + new ECB(2, 37)), + new ECBlocks(20, new ECB(4, 16), + new ECB(4, 17)), + new ECBlocks(24, new ECB(4, 12), + new ECB(4, 13))), + new Version(10, new int[]{6, 28, 50}, + new ECBlocks(18, new ECB(2, 68), + new ECB(2, 69)), + new ECBlocks(26, new ECB(4, 43), + new ECB(1, 44)), + new ECBlocks(24, new ECB(6, 19), + new ECB(2, 20)), + new ECBlocks(28, new ECB(6, 15), + new ECB(2, 16))), + new Version(11, new int[]{6, 30, 54}, + new ECBlocks(20, new ECB(4, 81)), + new ECBlocks(30, new ECB(1, 50), + new ECB(4, 51)), + new ECBlocks(28, new ECB(4, 22), + new ECB(4, 23)), + new ECBlocks(24, new ECB(3, 12), + new ECB(8, 13))), + new Version(12, new int[]{6, 32, 58}, + new ECBlocks(24, new ECB(2, 92), + new ECB(2, 93)), + new ECBlocks(22, new ECB(6, 36), + new ECB(2, 37)), + new ECBlocks(26, new ECB(4, 20), + new ECB(6, 21)), + new ECBlocks(28, new ECB(7, 14), + new ECB(4, 15))), + new Version(13, new int[]{6, 34, 62}, + new ECBlocks(26, new ECB(4, 107)), + new ECBlocks(22, new ECB(8, 37), + new ECB(1, 38)), + new ECBlocks(24, new ECB(8, 20), + new ECB(4, 21)), + new ECBlocks(22, new ECB(12, 11), + new ECB(4, 12))), + new Version(14, new int[]{6, 26, 46, 66}, + new ECBlocks(30, new ECB(3, 115), + new ECB(1, 116)), + new ECBlocks(24, new ECB(4, 40), + new ECB(5, 41)), + new ECBlocks(20, new ECB(11, 16), + new ECB(5, 17)), + new ECBlocks(24, new ECB(11, 12), + new ECB(5, 13))), + new Version(15, new int[]{6, 26, 48, 70}, + new ECBlocks(22, new ECB(5, 87), + new ECB(1, 88)), + new ECBlocks(24, new ECB(5, 41), + new ECB(5, 42)), + new ECBlocks(30, new ECB(5, 24), + new ECB(7, 25)), + new ECBlocks(24, new ECB(11, 12), + new ECB(7, 13))), + new Version(16, new int[]{6, 26, 50, 74}, + new ECBlocks(24, new ECB(5, 98), + new ECB(1, 99)), + new ECBlocks(28, new ECB(7, 45), + new ECB(3, 46)), + new ECBlocks(24, new ECB(15, 19), + new ECB(2, 20)), + new ECBlocks(30, new ECB(3, 15), + new ECB(13, 16))), + new Version(17, new int[]{6, 30, 54, 78}, + new ECBlocks(28, new ECB(1, 107), + new ECB(5, 108)), + new ECBlocks(28, new ECB(10, 46), + new ECB(1, 47)), + new ECBlocks(28, new ECB(1, 22), + new ECB(15, 23)), + new ECBlocks(28, new ECB(2, 14), + new ECB(17, 15))), + new Version(18, new int[]{6, 30, 56, 82}, + new ECBlocks(30, new ECB(5, 120), + new ECB(1, 121)), + new ECBlocks(26, new ECB(9, 43), + new ECB(4, 44)), + new ECBlocks(28, new ECB(17, 22), + new ECB(1, 23)), + new ECBlocks(28, new ECB(2, 14), + new ECB(19, 15))), + new Version(19, new int[]{6, 30, 58, 86}, + new ECBlocks(28, new ECB(3, 113), + new ECB(4, 114)), + new ECBlocks(26, new ECB(3, 44), + new ECB(11, 45)), + new ECBlocks(26, new ECB(17, 21), + new ECB(4, 22)), + new ECBlocks(26, new ECB(9, 13), + new ECB(16, 14))), + new Version(20, new int[]{6, 34, 62, 90}, + new ECBlocks(28, new ECB(3, 107), + new ECB(5, 108)), + new ECBlocks(26, new ECB(3, 41), + new ECB(13, 42)), + new ECBlocks(30, new ECB(15, 24), + new ECB(5, 25)), + new ECBlocks(28, new ECB(15, 15), + new ECB(10, 16))), + new Version(21, new int[]{6, 28, 50, 72, 94}, + new ECBlocks(28, new ECB(4, 116), + new ECB(4, 117)), + new ECBlocks(26, new ECB(17, 42)), + new ECBlocks(28, new ECB(17, 22), + new ECB(6, 23)), + new ECBlocks(30, new ECB(19, 16), + new ECB(6, 17))), + new Version(22, new int[]{6, 26, 50, 74, 98}, + new ECBlocks(28, new ECB(2, 111), + new ECB(7, 112)), + new ECBlocks(28, new ECB(17, 46)), + new ECBlocks(30, new ECB(7, 24), + new ECB(16, 25)), + new ECBlocks(24, new ECB(34, 13))), + new Version(23, new int[]{6, 30, 54, 74, 102}, + new ECBlocks(30, new ECB(4, 121), + new ECB(5, 122)), + new ECBlocks(28, new ECB(4, 47), + new ECB(14, 48)), + new ECBlocks(30, new ECB(11, 24), + new ECB(14, 25)), + new ECBlocks(30, new ECB(16, 15), + new ECB(14, 16))), + new Version(24, new int[]{6, 28, 54, 80, 106}, + new ECBlocks(30, new ECB(6, 117), + new ECB(4, 118)), + new ECBlocks(28, new ECB(6, 45), + new ECB(14, 46)), + new ECBlocks(30, new ECB(11, 24), + new ECB(16, 25)), + new ECBlocks(30, new ECB(30, 16), + new ECB(2, 17))), + new Version(25, new int[]{6, 32, 58, 84, 110}, + new ECBlocks(26, new ECB(8, 106), + new ECB(4, 107)), + new ECBlocks(28, new ECB(8, 47), + new ECB(13, 48)), + new ECBlocks(30, new ECB(7, 24), + new ECB(22, 25)), + new ECBlocks(30, new ECB(22, 15), + new ECB(13, 16))), + new Version(26, new int[]{6, 30, 58, 86, 114}, + new ECBlocks(28, new ECB(10, 114), + new ECB(2, 115)), + new ECBlocks(28, new ECB(19, 46), + new ECB(4, 47)), + new ECBlocks(28, new ECB(28, 22), + new ECB(6, 23)), + new ECBlocks(30, new ECB(33, 16), + new ECB(4, 17))), + new Version(27, new int[]{6, 34, 62, 90, 118}, + new ECBlocks(30, new ECB(8, 122), + new ECB(4, 123)), + new ECBlocks(28, new ECB(22, 45), + new ECB(3, 46)), + new ECBlocks(30, new ECB(8, 23), + new ECB(26, 24)), + new ECBlocks(30, new ECB(12, 15), + new ECB(28, 16))), + new Version(28, new int[]{6, 26, 50, 74, 98, 122}, + new ECBlocks(30, new ECB(3, 117), + new ECB(10, 118)), + new ECBlocks(28, new ECB(3, 45), + new ECB(23, 46)), + new ECBlocks(30, new ECB(4, 24), + new ECB(31, 25)), + new ECBlocks(30, new ECB(11, 15), + new ECB(31, 16))), + new Version(29, new int[]{6, 30, 54, 78, 102, 126}, + new ECBlocks(30, new ECB(7, 116), + new ECB(7, 117)), + new ECBlocks(28, new ECB(21, 45), + new ECB(7, 46)), + new ECBlocks(30, new ECB(1, 23), + new ECB(37, 24)), + new ECBlocks(30, new ECB(19, 15), + new ECB(26, 16))), + new Version(30, new int[]{6, 26, 52, 78, 104, 130}, + new ECBlocks(30, new ECB(5, 115), + new ECB(10, 116)), + new ECBlocks(28, new ECB(19, 47), + new ECB(10, 48)), + new ECBlocks(30, new ECB(15, 24), + new ECB(25, 25)), + new ECBlocks(30, new ECB(23, 15), + new ECB(25, 16))), + new Version(31, new int[]{6, 30, 56, 82, 108, 134}, + new ECBlocks(30, new ECB(13, 115), + new ECB(3, 116)), + new ECBlocks(28, new ECB(2, 46), + new ECB(29, 47)), + new ECBlocks(30, new ECB(42, 24), + new ECB(1, 25)), + new ECBlocks(30, new ECB(23, 15), + new ECB(28, 16))), + new Version(32, new int[]{6, 34, 60, 86, 112, 138}, + new ECBlocks(30, new ECB(17, 115)), + new ECBlocks(28, new ECB(10, 46), + new ECB(23, 47)), + new ECBlocks(30, new ECB(10, 24), + new ECB(35, 25)), + new ECBlocks(30, new ECB(19, 15), + new ECB(35, 16))), + new Version(33, new int[]{6, 30, 58, 86, 114, 142}, + new ECBlocks(30, new ECB(17, 115), + new ECB(1, 116)), + new ECBlocks(28, new ECB(14, 46), + new ECB(21, 47)), + new ECBlocks(30, new ECB(29, 24), + new ECB(19, 25)), + new ECBlocks(30, new ECB(11, 15), + new ECB(46, 16))), + new Version(34, new int[]{6, 34, 62, 90, 118, 146}, + new ECBlocks(30, new ECB(13, 115), + new ECB(6, 116)), + new ECBlocks(28, new ECB(14, 46), + new ECB(23, 47)), + new ECBlocks(30, new ECB(44, 24), + new ECB(7, 25)), + new ECBlocks(30, new ECB(59, 16), + new ECB(1, 17))), + new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150}, + new ECBlocks(30, new ECB(12, 121), + new ECB(7, 122)), + new ECBlocks(28, new ECB(12, 47), + new ECB(26, 48)), + new ECBlocks(30, new ECB(39, 24), + new ECB(14, 25)), + new ECBlocks(30, new ECB(22, 15), + new ECB(41, 16))), + new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154}, + new ECBlocks(30, new ECB(6, 121), + new ECB(14, 122)), + new ECBlocks(28, new ECB(6, 47), + new ECB(34, 48)), + new ECBlocks(30, new ECB(46, 24), + new ECB(10, 25)), + new ECBlocks(30, new ECB(2, 15), + new ECB(64, 16))), + new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158}, + new ECBlocks(30, new ECB(17, 122), + new ECB(4, 123)), + new ECBlocks(28, new ECB(29, 46), + new ECB(14, 47)), + new ECBlocks(30, new ECB(49, 24), + new ECB(10, 25)), + new ECBlocks(30, new ECB(24, 15), + new ECB(46, 16))), + new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162}, + new ECBlocks(30, new ECB(4, 122), + new ECB(18, 123)), + new ECBlocks(28, new ECB(13, 46), + new ECB(32, 47)), + new ECBlocks(30, new ECB(48, 24), + new ECB(14, 25)), + new ECBlocks(30, new ECB(42, 15), + new ECB(32, 16))), + new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166}, + new ECBlocks(30, new ECB(20, 117), + new ECB(4, 118)), + new ECBlocks(28, new ECB(40, 47), + new ECB(7, 48)), + new ECBlocks(30, new ECB(43, 24), + new ECB(22, 25)), + new ECBlocks(30, new ECB(10, 15), + new ECB(67, 16))), + new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170}, + new ECBlocks(30, new ECB(19, 118), + new ECB(6, 119)), + new ECBlocks(28, new ECB(18, 47), + new ECB(31, 48)), + new ECBlocks(30, new ECB(34, 24), + new ECB(34, 25)), + new ECBlocks(30, new ECB(20, 15), + new ECB(61, 16))) + }; + } + + } +} \ No newline at end of file diff --git a/csharp/qrcode/detector/AlignmentPattern.cs b/csharp/qrcode/detector/AlignmentPattern.cs new file mode 100755 index 000000000..562a1d2f4 --- /dev/null +++ b/csharp/qrcode/detector/AlignmentPattern.cs @@ -0,0 +1,56 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.detector +{ + public sealed class AlignmentPattern : ResultPoint + { + private float posX; + private float posY; + private float estimatedModuleSize; + + public AlignmentPattern(float posX, float posY, float estimatedModuleSize) { + this.posX = posX; + this.posY = posY; + this.estimatedModuleSize = estimatedModuleSize; + } + + public float getX() { + return posX; + } + + public float getY() { + return posY; + } + + /** + *

Determines if this alignment pattern "about equals" an alignment pattern at the stated + * position and size -- meaning, it is at nearly the same center with nearly the same size.

+ */ + public bool aboutEquals(float moduleSize, float i, float j) { + return + Math.Abs(i - posY) <= moduleSize && + Math.Abs(j - posX) <= moduleSize && + (Math.Abs(moduleSize - estimatedModuleSize) <= 1.0f || + Math.Abs(moduleSize - estimatedModuleSize) / estimatedModuleSize <= 0.1f); + } + } + + +} \ No newline at end of file diff --git a/csharp/qrcode/detector/AlignmentPatternFinder.cs b/csharp/qrcode/detector/AlignmentPatternFinder.cs new file mode 100755 index 000000000..ed7c41c49 --- /dev/null +++ b/csharp/qrcode/detector/AlignmentPatternFinder.cs @@ -0,0 +1,258 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.detector +{ + public sealed class AlignmentPatternFinder + { + private MonochromeBitmapSource image; + private System.Collections.ArrayList possibleCenters; + private int startX; + private int startY; + private int width; + private int height; + private float moduleSize; + private int[] crossCheckStateCount; + + /** + *

Creates a finder that will look in a portion of the whole image.

+ * + * @param image image to search + * @param startX left column from which to start searching + * @param startY top row from which to start searching + * @param width width of region to search + * @param height height of region to search + * @param moduleSize estimated module size so far + */ + public AlignmentPatternFinder(MonochromeBitmapSource image, + int startX, + int startY, + int width, + int height, + float moduleSize) { + this.image = image; + this.possibleCenters = new System.Collections.ArrayList(5); + this.startX = startX; + this.startY = startY; + this.width = width; + this.height = height; + this.moduleSize = moduleSize; + this.crossCheckStateCount = new int[3]; + } + + /** + *

This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since + * it's pretty performance-critical and so is written to be fast foremost.

+ * + * @return {@link AlignmentPattern} if found + * @throws ReaderException if not found + */ + public AlignmentPattern find() { + int startX = this.startX; + int height = this.height; + int maxJ = startX + width; + int middleI = startY + (height >> 1); + BitArray luminanceRow = new BitArray(width); + // We are looking for black/white/black modules in 1:1:1 ratio; + // this tracks the number of black/white/black modules seen so far + int[] stateCount = new int[3]; + for (int iGen = 0; iGen < height; iGen++) { + // Search from middle outwards + int i = middleI + ((iGen & 0x01) == 0 ? ((iGen + 1) >> 1) : -((iGen + 1) >> 1)); + image.getBlackRow(i, luminanceRow, startX, width); + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + int j = startX; + // Burn off leading white pixels before anything else; if we start in the middle of + // a white run, it doesn't make sense to count its length, since we don't know if the + // white run continued to the left of the start point + while (j < maxJ && !luminanceRow.get(j - startX)) { + j++; + } + int currentState = 0; + while (j < maxJ) { + if (luminanceRow.get(j - startX)) { + // Black pixel + if (currentState == 1) { // Counting black pixels + stateCount[currentState]++; + } else { // Counting white pixels + if (currentState == 2) { // A winner? + if (foundPatternCross(stateCount)) { // Yes + AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, j); + if (confirmed != null) { + return confirmed; + } + } + stateCount[0] = stateCount[2]; + stateCount[1] = 1; + stateCount[2] = 0; + currentState = 1; + } else { + stateCount[++currentState]++; + } + } + } else { // White pixel + if (currentState == 1) { // Counting black pixels + currentState++; + } + stateCount[currentState]++; + } + j++; + } + if (foundPatternCross(stateCount)) { + AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, maxJ); + if (confirmed != null) { + return confirmed; + } + } + + } + + // Hmm, nothing we saw was observed and confirmed twice. If we had + // any guess at all, return it. + if (!(possibleCenters.Count==0)) { + return (AlignmentPattern) possibleCenters[0]; + } + + throw new ReaderException(); + } + + /** + * Given a count of black/white/black pixels just seen and an end position, + * figures the location of the center of this black/white/black run. + */ + private static float centerFromEnd(int[] stateCount, int end) { + return (float) (end - stateCount[2]) - stateCount[1] / 2.0f; + } + + /** + * @param stateCount count of black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/1 ratios + * used by alignment patterns to be considered a match + */ + private bool foundPatternCross(int[] stateCount) { + float moduleSize = this.moduleSize; + float maxVariance = moduleSize / 2.0f; + for (int i = 0; i < 3; i++) { + if (Math.Abs(moduleSize - stateCount[i]) >= maxVariance) { + return false; + } + } + return true; + } + + /** + *

After a horizontal scan finds a potential alignment pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * alignment pattern to see if the same proportion is detected.

+ * + * @param startI row where an alignment pattern was detected + * @param centerJ center of the section that appears to cross an alignment pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of alignment pattern, or {@link Float#NaN} if not found + */ + private float crossCheckVertical(int startI, int centerJ, int maxCount, int originalStateCountTotal) { + MonochromeBitmapSource image = this.image; + + int maxI = image.getHeight(); + int[] stateCount = crossCheckStateCount; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + + // Start counting up from center + int i = startI; + while (i >= 0 && image.isBlack(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + // If already too many modules in this state or ran off the edge: + if (i < 0 || stateCount[1] > maxCount) { + return float.NaN; + } + while (i >= 0 && !image.isBlack(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return float.NaN; + } + + // Now also count down from center + i = startI + 1; + while (i < maxI && image.isBlack(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i++; + } + if (i == maxI || stateCount[1] > maxCount) { + return float.NaN; + } + while (i < maxI && !image.isBlack(centerJ, i) && stateCount[2] <= maxCount) { + stateCount[2]++; + i++; + } + if (stateCount[2] > maxCount) { + return float.NaN; + } + + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + if (5 * Math.Abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return float.NaN; + } + + return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : float.NaN; + } + + /** + *

This is called when a horizontal scan finds a possible alignment pattern. It will + * cross check with a vertical scan, and if successful, will see if this pattern had been + * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have + * found the alignment pattern.

+ * + * @param stateCount reading state module counts from horizontal scan + * @param i row where alignment pattern may be found + * @param j end of possible alignment pattern in row + * @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not + */ + private AlignmentPattern handlePossibleCenter(int[] stateCount, int i, int j) { + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2]; + float centerJ = centerFromEnd(stateCount, j); + float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1], stateCountTotal); + if (!Single.IsNaN(centerI)) + { + float estimatedModuleSize = (float) (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f; + int max = possibleCenters.Count; + for (int index = 0; index < max; index++) { + AlignmentPattern center = (AlignmentPattern) possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + return new AlignmentPattern(centerJ, centerI, estimatedModuleSize); + } + } + // Hadn't found this before; save it + possibleCenters.Add(new AlignmentPattern(centerJ, centerI, estimatedModuleSize)); + } + return null; + } + + + } +} \ No newline at end of file diff --git a/csharp/qrcode/detector/Detector.cs b/csharp/qrcode/detector/Detector.cs new file mode 100755 index 000000000..828aa8513 --- /dev/null +++ b/csharp/qrcode/detector/Detector.cs @@ -0,0 +1,362 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.detector +{ + using Version = com.google.zxing.qrcode.decoder.Version; + + public sealed class Detector + { + private MonochromeBitmapSource image; + + public Detector(MonochromeBitmapSource image) { + this.image = image; + } + + /** + *

Detects a QR Code in an image, simply.

+ * + * @return {@link DetectorResult} encapsulating results of detecting a QR Code + * @throws ReaderException if no QR Code can be found + */ + public DetectorResult detect(){ + try{ + return detect(null); + }catch(Exception e){ + throw new ReaderException(e.Message); + } + } + + /** + *

Detects a QR Code in an image, simply.

+ * + * @param hints optional hints to detector + * @return {@link DetectorResult} encapsulating results of detecting a QR Code + * @throws ReaderException if no QR Code can be found + */ + public DetectorResult detect(System.Collections.Hashtable hints) { + + MonochromeBitmapSource image = this.image; + if (!BlackPointEstimationMethod.TWO_D_SAMPLING.Equals(image.getLastEstimationMethod())) { + image.estimateBlackPoint(BlackPointEstimationMethod.TWO_D_SAMPLING, 0); + } + + FinderPatternFinder finder = new FinderPatternFinder(image); + FinderPatternInfo info = finder.find(hints); + + FinderPattern topLeft = info.getTopLeft(); + FinderPattern topRight = info.getTopRight(); + FinderPattern bottomLeft = info.getBottomLeft(); + + float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft); + if (moduleSize < 1.0f) { + throw new ReaderException(); + } + int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize); + + Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension); + int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7; + + AlignmentPattern alignmentPattern = null; + // Anything above version 1 has an alignment pattern + if (provisionalVersion.getAlignmentPatternCenters().Length > 0) { + + // Guess where a "bottom right" finder pattern would have been + float bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX(); + float bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY(); + + // Estimate that alignment pattern is closer by 3 modules + // from "bottom right" to known top left location + float correctionToTopLeft = 1.0f - 3.0f / (float) modulesBetweenFPCenters; + int estAlignmentX = (int) (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX())); + int estAlignmentY = (int) (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY())); + + // Kind of arbitrary -- expand search radius before giving up + for (int i = 4; i <= 16; i <<= 1) { + try { + alignmentPattern = findAlignmentInRegion(moduleSize, + estAlignmentX, + estAlignmentY, + (float) i); + break; + } catch (ReaderException re) { + // try next round + } + } + if (alignmentPattern == null) { + throw new ReaderException(); + } + + } + + BitMatrix bits = sampleGrid(image, topLeft, topRight, bottomLeft, alignmentPattern, dimension); + + ResultPoint[] points; + if (alignmentPattern == null) { + points = new ResultPoint[]{bottomLeft, topLeft, topRight}; + } else { + points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern}; + } + return new DetectorResult(bits, points); + } + + private static BitMatrix sampleGrid(MonochromeBitmapSource image, + ResultPoint topLeft, + ResultPoint topRight, + ResultPoint bottomLeft, + ResultPoint alignmentPattern, + int dimension) { + float dimMinusThree = (float) dimension - 3.5f; + float bottomRightX; + float bottomRightY; + float sourceBottomRightX; + float sourceBottomRightY; + if (alignmentPattern != null) { + bottomRightX = alignmentPattern.getX(); + bottomRightY = alignmentPattern.getY(); + sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3.0f; + } else { + // Don't have an alignment pattern, just make up the bottom-right point + bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX(); + bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY(); + sourceBottomRightX = sourceBottomRightY = dimMinusThree; + } + + GridSampler sampler = GridSampler.Instance; + return sampler.sampleGrid( + image, + dimension, + 3.5f, + 3.5f, + dimMinusThree, + 3.5f, + sourceBottomRightX, + sourceBottomRightY, + 3.5f, + dimMinusThree, + topLeft.getX(), + topLeft.getY(), + topRight.getX(), + topRight.getY(), + bottomRightX, + bottomRightY, + bottomLeft.getX(), + bottomLeft.getY()); + } + + /** + *

Computes the dimension (number of modules on a size) of the QR Code based on the position + * of the finder patterns and estimated module size.

+ */ + private static int computeDimension(ResultPoint topLeft, + ResultPoint topRight, + ResultPoint bottomLeft, + float moduleSize) { + int tltrCentersDimension = round(GenericResultPoint.distance(topLeft, topRight) / moduleSize); + int tlblCentersDimension = round(GenericResultPoint.distance(topLeft, bottomLeft) / moduleSize); + int dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7; + switch (dimension & 0x03) { // mod 4 + case 0: + dimension++; + break; + // 1? do nothing + case 2: + dimension--; + break; + case 3: + throw new ReaderException(); + } + return dimension; + } + + /** + *

Computes an average estimated module size based on estimated derived from the positions + * of the three finder patterns.

+ */ + private float calculateModuleSize(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft) { + // Take the average + return (calculateModuleSizeOneWay(topLeft, topRight) + + calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f; + } + + /** + *

Estimates module size based on two finder patterns -- it uses + * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the + * width of each, measuring along the axis between their centers.

+ */ + private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern) { + float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(), + (int) pattern.getY(), + (int) otherPattern.getX(), + (int) otherPattern.getY()); + float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(), + (int) otherPattern.getY(), + (int) pattern.getX(), + (int) pattern.getY()); + if (Single.IsNaN(moduleSizeEst1)) { + return moduleSizeEst2; + } + if (Single.IsNaN(moduleSizeEst2)) + { + return moduleSizeEst1; + } + // Average them, and divide by 7 since we've counted the width of 3 black modules, + // and 1 white and 1 black module on either side. Ergo, divide sum by 14. + return (moduleSizeEst1 + moduleSizeEst2) / 14.0f; + } + + /** + * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of + * a finder pattern by looking for a black-white-black run from the center in the direction + * of another point (another finder pattern center), and in the opposite direction too.

+ */ + private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) { + + float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY); + + // Now count other way -- don't run off image though of course + int otherToX = fromX - (toX - fromX); + if (otherToX < 0) { + // "to" should the be the first value not included, so, the first value off + // the edge is -1 + otherToX = -1; + } else if (otherToX >= image.getWidth()) { + otherToX = image.getWidth(); + } + int otherToY = fromY - (toY - fromY); + if (otherToY < 0) { + otherToY = -1; + } else if (otherToY >= image.getHeight()) { + otherToY = image.getHeight(); + } + result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY); + return result - 1.0f; // -1 because we counted the middle pixel twice + } + + /** + *

This method traces a line from a point in the image, in the direction towards another point. + * It begins in a black region, and keeps going until it finds white, then black, then white again. + * It reports the distance from the start to this point.

+ * + *

This is used when figuring out how wide a finder pattern is, when the finder pattern + * may be skewed or rotated.

+ */ + private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) { + // Mild variant of Bresenham's algorithm; + // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm + 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 state = 0; // In black pixels, looking for white, first or second time + int diffX =0; + int diffY =0; + + for (int x = fromX, y = fromY; x != toX; x += xstep) { + + int realX = steep ? y : x; + int realY = steep ? x : y; + if (state == 1) { // In white pixels, looking for black + if (image.isBlack(realX, realY)) { + state++; + } + } else { + if (!image.isBlack(realX, realY)) { + state++; + } + } + + if (state == 3) { // Found black, white, black, and stumbled back onto white; done + diffX = x - fromX; + diffY = y - fromY; + return (float) Math.Sqrt((double) (diffX * diffX + diffY * diffY)); + } + error += dy; + if (error > 0) { + y += ystep; + error -= dx; + } + } + + diffX = toX - fromX; + diffY = toY - fromY; + return (float) Math.Sqrt((double) (diffX * diffX + diffY * diffY)); + } + + /** + *

Attempts to locate an alignment pattern in a limited region of the image, which is + * guessed to contain it. This method uses {@link AlignmentPattern}.

+ * + * @param overallEstModuleSize estimated module size so far + * @param estAlignmentX x coordinate of center of area probably containing alignment pattern + * @param estAlignmentY y coordinate of above + * @param allowanceFactor number of pixels in all directons to search from the center + * @return {@link AlignmentPattern} if found, or null otherwise + * @throws ReaderException if an unexpected error occurs during detection + */ + private AlignmentPattern findAlignmentInRegion(float overallEstModuleSize, + int estAlignmentX, + int estAlignmentY, + float allowanceFactor){ + // Look for an alignment pattern (3 modules in size) around where it + // should be + int allowance = (int) (allowanceFactor * overallEstModuleSize); + int alignmentAreaLeftX = Math.Max(0, estAlignmentX - allowance); + int alignmentAreaRightX = Math.Min(image.getWidth() - 1, estAlignmentX + allowance); + if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) { + throw new ReaderException(); + } + + int alignmentAreaTopY = Math.Max(0, estAlignmentY - allowance); + int alignmentAreaBottomY = Math.Min(image.getHeight() - 1, estAlignmentY + allowance); + + AlignmentPatternFinder alignmentFinder = + new AlignmentPatternFinder( + image, + alignmentAreaLeftX, + alignmentAreaTopY, + alignmentAreaRightX - alignmentAreaLeftX, + alignmentAreaBottomY - alignmentAreaTopY, + overallEstModuleSize); + return alignmentFinder.find(); + } + + /** + * Ends up being a bit faster than Math.round(). This merely rounds its argument to the nearest int, + * where x.5 rounds up. + */ + private static int round(float d) { + return (int) (d + 0.5f); + } + + } + + +} \ No newline at end of file diff --git a/csharp/qrcode/detector/FinderPattern.cs b/csharp/qrcode/detector/FinderPattern.cs new file mode 100755 index 000000000..601bcfbf7 --- /dev/null +++ b/csharp/qrcode/detector/FinderPattern.cs @@ -0,0 +1,71 @@ +/* +* 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 com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.detector +{ + public sealed class FinderPattern : ResultPoint + { + private float posX; + private float posY; + private float estimatedModuleSize; + private int count; + + public FinderPattern(float posX, float posY, float estimatedModuleSize) { + this.posX = posX; + this.posY = posY; + this.estimatedModuleSize = estimatedModuleSize; + this.count = 1; + } + + public float getX() { + return posX; + } + + public float getY() { + return posY; + } + + public float getEstimatedModuleSize() + { + return estimatedModuleSize; + } + + public int getCount() + { + return count; + } + + public void incrementCount() + { + this.count++; + } + + /** + *

Determines if this finder pattern "about equals" a finder pattern at the stated + * position and size -- meaning, it is at nearly the same center with nearly the same size.

+ */ + public bool aboutEquals(float moduleSize, float i, float j) { + return Math.Abs(i - posY) <= moduleSize && + Math.Abs(j - posX) <= moduleSize && + (Math.Abs(moduleSize - estimatedModuleSize) <= 1.0f || + Math.Abs(moduleSize - estimatedModuleSize) / estimatedModuleSize <= 0.1f); + } + + } +} \ No newline at end of file diff --git a/csharp/qrcode/detector/FinderPatternFinder.cs b/csharp/qrcode/detector/FinderPatternFinder.cs new file mode 100755 index 000000000..f9e3834bd --- /dev/null +++ b/csharp/qrcode/detector/FinderPatternFinder.cs @@ -0,0 +1,527 @@ +/* +* 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. +*/ +namespace com.google.zxing.qrcode.detector +{ + using System; + using com.google.zxing; + using com.google.zxing.common; + + public sealed class FinderPatternFinder + { + private static int CENTER_QUORUM = 2; + private static int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center + private static int MAX_MODULES = 57; // support up to version 10 for mobile clients + private static int INTEGER_MATH_SHIFT = 8; + + private MonochromeBitmapSource image; + private System.Collections.ArrayList possibleCenters; + private bool hasSkipped; + private int[] crossCheckStateCount; + + /** + *

Creates a finder that will search the image for three finder patterns.

+ * + * @param image image to search + */ + public FinderPatternFinder(MonochromeBitmapSource image) { + this.image = image; + this.possibleCenters = new System.Collections.ArrayList(); + this.crossCheckStateCount = new int[5]; + } + + public FinderPatternInfo find(System.Collections.Hashtable hints) { + bool tryHarder = hints != null && hints.ContainsKey(DecodeHintType.TRY_HARDER); + int maxI = image.getHeight(); + int maxJ = image.getWidth(); + // We are looking for black/white/black/white/black modules in + // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far + + // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the + // image, and then account for the center being 3 modules in size. This gives the smallest + // number of pixels the center could be, so skip this often. When trying harder, look for all + // QR versions regardless of how dense they are. + int iSkip = (int) (maxI / (MAX_MODULES * 4.0f) * 3); + if (iSkip < MIN_SKIP || tryHarder) { + iSkip = MIN_SKIP; + } + + bool done = false; + int[] stateCount = new int[5]; + BitArray blackRow = new BitArray(maxJ); + for (int i = iSkip - 1; i < maxI && !done; i += iSkip) { + // Get a row of black/white values + blackRow = image.getBlackRow(i, blackRow, 0, maxJ); + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + stateCount[3] = 0; + stateCount[4] = 0; + int currentState = 0; + for (int j = 0; j < maxJ; j++) { + if (blackRow.get(j)) { + // Black pixel + if ((currentState & 1) == 1) { // Counting white pixels + currentState++; + } + stateCount[currentState]++; + } else { // White pixel + if ((currentState & 1) == 0) { // Counting black pixels + if (currentState == 4) { // A winner? + if (foundPatternCross(stateCount)) { // Yes + bool confirmed = handlePossibleCenter(stateCount, i, j); + if (confirmed) { + // Start examining every other line. Checking each line turned out to be too + // expensive and didn't improve performance. + iSkip = 2; + if (hasSkipped) { + done = haveMulitplyConfirmedCenters(); + } else { + int rowSkip = findRowSkip(); + if (rowSkip > stateCount[2]) { + // Skip rows between row of lower confirmed center + // and top of presumed third confirmed center + // but back up a bit to get a full chance of detecting + // it, entire width of center of finder pattern + + // Skip by rowSkip, but back off by stateCount[2] (size of last center + // of pattern we saw) to be conservative, and also back off by iSkip which + // is about to be re-added + i += rowSkip - stateCount[2] - iSkip; + j = maxJ - 1; + } + } + } else { + // Advance to next black pixel + do { + j++; + } while (j < maxJ && !blackRow.get(j)); + j--; // back up to that last white pixel + } + // Clear state to start looking again + currentState = 0; + stateCount[0] = 0; + stateCount[1] = 0; + stateCount[2] = 0; + stateCount[3] = 0; + stateCount[4] = 0; + } else { // No, shift counts back by two + stateCount[0] = stateCount[2]; + stateCount[1] = stateCount[3]; + stateCount[2] = stateCount[4]; + stateCount[3] = 1; + stateCount[4] = 0; + currentState = 3; + } + } else { + stateCount[++currentState]++; + } + } else { // Counting white pixels + stateCount[currentState]++; + } + } + } + if (foundPatternCross(stateCount)) { + bool confirmed = handlePossibleCenter(stateCount, i, maxJ); + if (confirmed) { + iSkip = stateCount[0]; + if (hasSkipped) { + // Found a third one + done = haveMulitplyConfirmedCenters(); + } + } + } + } + + FinderPattern[] patternInfo = selectBestPatterns(); + GenericResultPoint.orderBestPatterns(patternInfo); + + return new FinderPatternInfo(patternInfo); + } + + /** + * Given a count of black/white/black/white/black pixels just seen and an end position, + * figures the location of the center of this run. + */ + private static float centerFromEnd(int[] stateCount, int end) { + return (float) (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f; + } + + /** + * @param stateCount count of black/white/black/white/black pixels just read + * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios + * used by finder patterns to be considered a match + */ + private static bool foundPatternCross(int[] stateCount) { + int totalModuleSize = 0; + for (int i = 0; i < 5; i++) { + int count = stateCount[i]; + if (count == 0) { + return false; + } + totalModuleSize += count; + } + if (totalModuleSize < 7) { + return false; + } + int moduleSize = (totalModuleSize << INTEGER_MATH_SHIFT) / 7; + int maxVariance = moduleSize / 2; + // Allow less than 50% variance from 1-1-3-1-1 proportions + return Math.Abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance && + Math.Abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance && + Math.Abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance && + Math.Abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance && + Math.Abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance; + } + + private int[] getCrossCheckStateCount() { + crossCheckStateCount[0] = 0; + crossCheckStateCount[1] = 0; + crossCheckStateCount[2] = 0; + crossCheckStateCount[3] = 0; + crossCheckStateCount[4] = 0; + return crossCheckStateCount; + } + + /** + *

After a horizontal scan finds a potential finder pattern, this method + * "cross-checks" by scanning down vertically through the center of the possible + * finder pattern to see if the same proportion is detected.

+ * + * @param startI row where a finder pattern was detected + * @param centerJ center of the section that appears to cross a finder pattern + * @param maxCount maximum reasonable number of modules that should be + * observed in any reading state, based on the results of the horizontal scan + * @return vertical center of finder pattern, or {@link Float#NaN} if not found + */ + private float crossCheckVertical(int startI, int centerJ, int maxCount, int originalStateCountTotal) { + MonochromeBitmapSource image = this.image; + + int maxI = image.getHeight(); + int[] stateCount = getCrossCheckStateCount(); + + // Start counting up from center + int i = startI; + while (i >= 0 && image.isBlack(centerJ, i)) { + stateCount[2]++; + i--; + } + if (i < 0) { + return float.NaN; + } + while (i >= 0 && !image.isBlack(centerJ, i) && stateCount[1] <= maxCount) { + stateCount[1]++; + i--; + } + // If already too many modules in this state or ran off the edge: + if (i < 0 || stateCount[1] > maxCount) { + return float.NaN; + } + while (i >= 0 && image.isBlack(centerJ, i) && stateCount[0] <= maxCount) { + stateCount[0]++; + i--; + } + if (stateCount[0] > maxCount) { + return float.NaN; + } + + // Now also count down from center + i = startI + 1; + while (i < maxI && image.isBlack(centerJ, i)) { + stateCount[2]++; + i++; + } + if (i == maxI) { + return float.NaN; + } + while (i < maxI && !image.isBlack(centerJ, i) && stateCount[3] < maxCount) { + stateCount[3]++; + i++; + } + if (i == maxI || stateCount[3] >= maxCount) { + return float.NaN; + } + while (i < maxI && image.isBlack(centerJ, i) && stateCount[4] < maxCount) { + stateCount[4]++; + i++; + } + if (stateCount[4] >= maxCount) { + return float.NaN; + } + + // If we found a finder-pattern-like section, but its size is more than 20% different than + // the original, assume it's a false positive + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + if (5 * Math.Abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return float.NaN; + } + + return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : float.NaN; + } + + /** + *

Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical, + * except it reads horizontally instead of vertically. This is used to cross-cross + * check a vertical cross check and locate the real center of the alignment pattern.

+ */ + private float crossCheckHorizontal(int startJ, int centerI, int maxCount, int originalStateCountTotal) { + MonochromeBitmapSource image = this.image; + + int maxJ = image.getWidth(); + int[] stateCount = getCrossCheckStateCount(); + + int j = startJ; + while (j >= 0 && image.isBlack(j, centerI)) { + stateCount[2]++; + j--; + } + if (j < 0) { + return float.NaN; + } + while (j >= 0 && !image.isBlack(j, centerI) && stateCount[1] <= maxCount) { + stateCount[1]++; + j--; + } + if (j < 0 || stateCount[1] > maxCount) { + return float.NaN; + } + while (j >= 0 && image.isBlack(j, centerI) && stateCount[0] <= maxCount) { + stateCount[0]++; + j--; + } + if (stateCount[0] > maxCount) { + return float.NaN; + } + + j = startJ + 1; + while (j < maxJ && image.isBlack(j, centerI)) { + stateCount[2]++; + j++; + } + if (j == maxJ) { + return float.NaN; + } + while (j < maxJ && !image.isBlack(j, centerI) && stateCount[3] < maxCount) { + stateCount[3]++; + j++; + } + if (j == maxJ || stateCount[3] >= maxCount) { + return float.NaN; + } + while (j < maxJ && image.isBlack(j, centerI) && stateCount[4] < maxCount) { + stateCount[4]++; + j++; + } + if (stateCount[4] >= maxCount) { + return float.NaN; + } + + // If we found a finder-pattern-like section, but its size is significantly different than + // the original, assume it's a false positive + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + if (5 * Math.Abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) { + return float.NaN; + } + + return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : float.NaN; + } + + /** + *

This is called when a horizontal scan finds a possible alignment pattern. It will + * cross check with a vertical scan, and if successful, will, ah, cross-cross-check + * with another horizontal scan. This is needed primarily to locate the real horizontal + * center of the pattern in cases of extreme skew.

+ * + *

If that succeeds the finder pattern location is added to a list that tracks + * the number of times each location has been nearly-matched as a finder pattern. + * Each additional find is more evidence that the location is in fact a finder + * pattern center + * + * @param stateCount reading state module counts from horizontal scan + * @param i row where finder pattern may be found + * @param j end of possible finder pattern in row + * @return true if a finder pattern candidate was found this time + */ + private bool handlePossibleCenter(int[] stateCount, + int i, + int j) { + int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4]; + float centerJ = centerFromEnd(stateCount, j); + float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2], stateCountTotal); + if (!Single.IsNaN(centerI)) { + // Re-cross check + centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2], stateCountTotal); + if (!Single.IsNaN(centerJ)) + { + float estimatedModuleSize = (float) stateCountTotal / 7.0f; + bool found = false; + int max = possibleCenters.Count; + for (int index = 0; index < max; index++) { + FinderPattern center = (FinderPattern) possibleCenters[index]; + // Look for about the same center and module size: + if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) { + center.incrementCount(); + found = true; + break; + } + } + if (!found) { + possibleCenters.Add(new FinderPattern(centerJ, centerI, estimatedModuleSize)); + } + return true; + } + } + return false; + } + + /** + * @return number of rows we could safely skip during scanning, based on the first + * two finder patterns that have been located. In some cases their position will + * allow us to infer that the third pattern must lie below a certain point farther + * down in the image. + */ + private int findRowSkip() { + int max = possibleCenters.Count; + if (max <= 1) { + return 0; + } + FinderPattern firstConfirmedCenter = null; + for (int i = 0; i < max; i++) { + FinderPattern center = (FinderPattern) possibleCenters[i]; + if (center.getCount() >= CENTER_QUORUM) { + if (firstConfirmedCenter == null) { + firstConfirmedCenter = center; + } else { + // We have two confirmed centers + // How far down can we skip before resuming looking for the next + // pattern? In the worst case, only the difference between the + // difference in the x / y coordinates of the two centers. + // This is the case where you find top left first. Draw it out. + hasSkipped = true; + return (int) (Math.Abs(firstConfirmedCenter.getX() - center.getX()) - + Math.Abs(firstConfirmedCenter.getY() - center.getY())); + } + } + } + return 0; + } + + /** + * @return true iff we have found at least 3 finder patterns that have been detected + * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the + * candidates is "pretty similar" + */ + private bool haveMulitplyConfirmedCenters() { + int confirmedCount = 0; + float totalModuleSize = 0.0f; + int max = possibleCenters.Count; + for (int i = 0; i < max; i++) { + FinderPattern pattern = (FinderPattern) possibleCenters[i]; + if (pattern.getCount() >= CENTER_QUORUM) { + confirmedCount++; + totalModuleSize += pattern.getEstimatedModuleSize(); + } + } + if (confirmedCount < 3) { + return false; + } + // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive" + // and that we need to keep looking. We detect this by asking if the estimated module sizes + // vary too much. We arbitrarily say that when the total deviation from average exceeds + // 15% of the total module size estimates, it's too much. + float average = totalModuleSize / max; + float totalDeviation = 0.0f; + for (int i = 0; i < max; i++) { + FinderPattern pattern = (FinderPattern) possibleCenters[i]; + totalDeviation += Math.Abs(pattern.getEstimatedModuleSize() - average); + } + return totalDeviation <= 0.15f * totalModuleSize; + } + + /** + * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are + * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module + * size differs from the average among those patterns the least + * @throws ReaderException if 3 such finder patterns do not exist + */ + private FinderPattern[] selectBestPatterns(){ + Collections.insertionSort(possibleCenters, new CenterComparator()); + int size = 0; + int max = possibleCenters.Count; + while (size < max) { + if (((FinderPattern) possibleCenters[size]).getCount() < CENTER_QUORUM) { + break; + } + size++; + } + + if (size < 3) { + // Couldn't find enough finder patterns + throw new ReaderException(); + } + + if (size > 3) { + // Throw away all but those first size candidate points we found. + SupportClass.SetCapacity(possibleCenters, size); + // We need to pick the best three. Find the most + // popular ones whose module size is nearest the average + float averageModuleSize = 0.0f; + for (int i = 0; i < size; i++) { + averageModuleSize += ((FinderPattern) possibleCenters[i]).getEstimatedModuleSize(); + } + averageModuleSize /= (float) size; + // We don't have java.util.Collections in J2ME + Collections.insertionSort(possibleCenters, new ClosestToAverageComparator(averageModuleSize)); + } + + return new FinderPattern[]{ + (FinderPattern) possibleCenters[0], + (FinderPattern) possibleCenters[1], + (FinderPattern) possibleCenters[2] + }; + } + + /** + *

Orders by {@link FinderPattern#getCount()}, descending.

+ */ + private class CenterComparator : Comparator { + public int compare(object center1, object center2) + { + return ((FinderPattern) center2).getCount() - ((FinderPattern) center1).getCount(); + } + } + + /** + *

Orders by variance from average module size, ascending.

+ */ + private class ClosestToAverageComparator : Comparator { + private float averageModuleSize; + + public ClosestToAverageComparator(float averageModuleSize) { + this.averageModuleSize = averageModuleSize; + } + + public int compare(object center1, object center2) + { + return Math.Abs(((FinderPattern) center1).getEstimatedModuleSize() - averageModuleSize) < + Math.Abs(((FinderPattern) center2).getEstimatedModuleSize() - averageModuleSize) ? + -1 : + 1; + } + } + + } + +} \ No newline at end of file diff --git a/csharp/qrcode/detector/FinderPatternInfo.cs b/csharp/qrcode/detector/FinderPatternInfo.cs new file mode 100755 index 000000000..5faae93dd --- /dev/null +++ b/csharp/qrcode/detector/FinderPatternInfo.cs @@ -0,0 +1,51 @@ +/* +* 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. +*/ +namespace com.google.zxing.qrcode.detector +{ + using com.google.zxing; + using com.google.zxing.common; + + public sealed class FinderPatternInfo + { + private FinderPattern bottomLeft; + private FinderPattern topLeft; + private FinderPattern topRight; + + public FinderPatternInfo(FinderPattern[] patternCenters) { + this.bottomLeft = patternCenters[0]; + this.topLeft = patternCenters[1]; + this.topRight = patternCenters[2]; + } + + public FinderPattern getBottomLeft() + { + return bottomLeft; + } + + public FinderPattern getTopLeft() + { + return topLeft; + } + + public FinderPattern getTopRight() + { + return topRight; + } + + } + +} + diff --git a/csharp/qrcode/encoder/BitVector.cs b/csharp/qrcode/encoder/BitVector.cs new file mode 100755 index 000000000..32b5ea683 --- /dev/null +++ b/csharp/qrcode/encoder/BitVector.cs @@ -0,0 +1,151 @@ +/* +* 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.Text; +using com.google.zxing; +using com.google.zxing.common; + +namespace com.google.zxing.qrcode.encoder +{ + public sealed class BitVector { + + private int sizeInBits; + private sbyte[] array; + + // For efficiency, start out with some room to work. + private static int DEFAULT_SIZE_IN_BYTES = 32; + + public BitVector() { + sizeInBits = 0; + array = new sbyte[DEFAULT_SIZE_IN_BYTES]; + } + + // Return the bit value at "index". + public int at(int index) { + if (index < 0 || index >= sizeInBits) { + throw new ArgumentException("Bad index: " + index); + } + int value = array[index >> 3] & 0xff; + return (value >> (7 - (index & 0x7))) & 1; + } + + // Return the number of bits in the bit vector. + public int size() { + return sizeInBits; + } + + // Return the number of bytes in the bit vector. + public int sizeInBytes() { + return (sizeInBits + 7) >> 3; + } + + // Append one bit to the bit vector. + public void appendBit(int bit) { + if (!(bit == 0 || bit == 1)) { + throw new ArgumentException("Bad bit"); + } + int numBitsInLastByte = sizeInBits & 0x7; + // We'll expand array if we don't have bits in the last byte. + if (numBitsInLastByte == 0) { + appendByte(0); + sizeInBits -= 8; + } + // Modify the last byte. + array[sizeInBits >> 3] |= (sbyte)(bit << (7 - numBitsInLastByte)); + ++sizeInBits; + } + + // Append "numBits" bits in "value" to the bit vector. + // REQUIRES: 0<= numBits <= 32. + // + // Examples: + // - appendBits(0x00, 1) adds 0. + // - appendBits(0x00, 4) adds 0000. + // - appendBits(0xff, 8) adds 11111111. + public void appendBits(int value, int numBits) { + if (numBits < 0 || numBits > 32) { + throw new ArgumentException("Num bits must be between 0 and 32"); + } + int numBitsLeft = numBits; + while (numBitsLeft > 0) { + // Optimization for byte-oriented appending. + if ((sizeInBits & 0x7) == 0 && numBitsLeft >= 8) { + int newByte = (value >> (numBitsLeft - 8)) & 0xff; + appendByte(newByte); + numBitsLeft -= 8; + } else { + int bit = (value >> (numBitsLeft - 1)) & 1; + appendBit(bit); + --numBitsLeft; + } + } + } + + // Append "bits". + public void appendBitVector(BitVector bits) { + int size = bits.size(); + for (int i = 0; i < size; ++i) { + appendBit(bits.at(i)); + } + } + + // Modify the bit vector by XOR'ing with "other" + public void xor(BitVector other) { + if (sizeInBits != other.size()) { + throw new ArgumentException("BitVector sizes don't match"); + } + int sizeInBytes = (sizeInBits + 7) >> 3; + for (int i = 0; i < sizeInBytes; ++i) { + // The last byte could be incomplete (i.e. not have 8 bits in + // it) but there is no problem since 0 XOR 0 == 0. + array[i] ^= other.array[i]; + } + } + + // Return String like "01110111" for debugging. + public String toString() { + StringBuilder result = new StringBuilder(sizeInBits); + for (int i = 0; i < sizeInBits; ++i) { + if (at(i) == 0) { + result.Append('0'); + } else if (at(i) == 1) { + result.Append('1'); + } else { + throw new ArgumentException("Byte isn't 0 or 1"); + } + } + return result.ToString(); + } + + // Callers should not assume that array.length is the exact number of bytes needed to hold + // sizeInBits - it will typically be larger for efficiency. + public sbyte[] getArray() { + return array; + } + + // Add a new byte to the end, possibly reallocating and doubling the size of the array if we've + // run out of room. + private void appendByte(int value) { + if ((sizeInBits >> 3) == array.Length) { + sbyte[] newArray = new sbyte[(array.Length << 1)]; + System.Array.Copy (array, 0, newArray, 0, array.Length); + array = newArray; + } + array[sizeInBits >> 3] = (sbyte) value; + sizeInBits += 8; + } + } +} \ No newline at end of file diff --git a/csharp/qrcode/encoder/Encoder.cs b/csharp/qrcode/encoder/Encoder.cs new file mode 100755 index 000000000..df0b30368 --- /dev/null +++ b/csharp/qrcode/encoder/Encoder.cs @@ -0,0 +1,511 @@ +/* +* 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.Text; +using System.Collections; +using com.google.zxing; +using com.google.zxing.common; +using com.google.zxing.common.reedsolomon; +using com.google.zxing.qrcode.decoder; +using com.google.zxing.qrcode; + +namespace com.google.zxing.qrcode.encoder +{ + using Version=com.google.zxing.qrcode.decoder.Version; + public sealed class Encoder + { + // The original table is defined in the table 5 of JISX0510:2004 (p.19). + private static int[] ALPHANUMERIC_TABLE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f + }; + + private Encoder() { + } + + // The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details. + // Basically it applies four rules and summate all penalties. + private static int calculateMaskPenalty(ByteMatrix matrix) { + int penalty = 0; + penalty += MaskUtil.applyMaskPenaltyRule1(matrix); + penalty += MaskUtil.applyMaskPenaltyRule2(matrix); + penalty += MaskUtil.applyMaskPenaltyRule3(matrix); + penalty += MaskUtil.applyMaskPenaltyRule4(matrix); + return penalty; + } + + private class BlockPair { + + private ByteArray dataBytes; + private ByteArray errorCorrectionBytes; + + public BlockPair(ByteArray data, ByteArray errorCorrection) { + dataBytes = data; + errorCorrectionBytes = errorCorrection; + } + + public ByteArray getDataBytes() { + return dataBytes; + } + + public ByteArray getErrorCorrectionBytes() { + return errorCorrectionBytes; + } + + } + + // Encode "bytes" with the error correction level "getECLevel". The encoding mode will be chosen + // internally by chooseMode(). On success, store the result in "qrCode" and return true. + // We recommend you to use QRCode.EC_LEVEL_L (the lowest level) for + // "getECLevel" since our primary use is to show QR code on desktop screens. We don't need very + // strong error correction for this purpose. + // + // Note that there is no way to encode bytes in MODE_KANJI. We might want to add EncodeWithMode() + // with which clients can specify the encoding mode. For now, we don't need the functionality. + public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode) + { + // Step 1: Choose the mode (encoding). + Mode mode = chooseMode(content); + + // Step 2: Append "bytes" into "dataBits" in appropriate encoding. + BitVector dataBits = new BitVector(); + appendBytes(content, mode, dataBits); + // Step 3: Initialize QR code that can contain "dataBits". + int numInputBytes = dataBits.sizeInBytes(); + initQRCode(numInputBytes, ecLevel, mode, qrCode); + + // Step 4: Build another bit vector that contains header and data. + BitVector headerAndDataBits = new BitVector(); + appendModeInfo(qrCode.getMode(), headerAndDataBits); + appendLengthInfo(content.Length, qrCode.getVersion(), qrCode.getMode(), headerAndDataBits); + headerAndDataBits.appendBitVector(dataBits); + + // Step 5: Terminate the bits properly. + terminateBits(qrCode.getNumDataBytes(), headerAndDataBits); + + // Step 6: Interleave data bits with error correction code. + BitVector finalBits = new BitVector(); + interleaveWithECBytes(headerAndDataBits, qrCode.getNumTotalBytes(), qrCode.getNumDataBytes(), + qrCode.getNumRSBlocks(), finalBits); + + // Step 7: Choose the mask pattern and set to "qrCode". + ByteMatrix matrix = new ByteMatrix(qrCode.getMatrixWidth(), qrCode.getMatrixWidth()); + qrCode.setMaskPattern(chooseMaskPattern(finalBits, qrCode.getECLevel(), qrCode.getVersion(), + matrix)); + + // Step 8. Build the matrix and set it to "qrCode". + MatrixUtil.buildMatrix(finalBits, qrCode.getECLevel(), qrCode.getVersion(), + qrCode.getMaskPattern(), matrix); + qrCode.setMatrix(matrix); + // Step 9. Make sure we have a valid QR Code. + if (!qrCode.isValid()) { + throw new WriterException("Invalid QR code: " + qrCode.toString()); + } + } + + // Return the code point of the table used in alphanumeric mode. Return -1 if there is no + // corresponding code in the table. + static int getAlphanumericCode(int code) { + if (code < ALPHANUMERIC_TABLE.Length) { + return ALPHANUMERIC_TABLE[code]; + } + return -1; + } + + // Choose the best mode by examining the content. + // + // Note that this function does not return MODE_KANJI, as we cannot distinguish Shift_JIS from + // other encodings such as ISO-8859-1, from data bytes alone. For example "\xE0\xE0" can be + // interpreted as one character in Shift_JIS, but also two characters in ISO-8859-1. + // + // JAVAPORT: This MODE_KANJI limitation sounds like a problem for us. + public static Mode chooseMode(String content) { + bool hasNumeric = false; + bool hasAlphanumeric = false; + for (int i = 0; i < content.Length; ++i) { + char c = content[i]; + if (c >= '0' && c <= '9') { + hasNumeric = true; + } else if (getAlphanumericCode(c) != -1) { + hasAlphanumeric = true; + } else { + return Mode.BYTE; + } + } + if (hasAlphanumeric) { + return Mode.ALPHANUMERIC; + } else if (hasNumeric) { + return Mode.NUMERIC; + } + return Mode.BYTE; + } + + private static int chooseMaskPattern(BitVector bits, ErrorCorrectionLevel ecLevel, int version,ByteMatrix matrix){ + try{ + int minPenalty = int.MaxValue; // Lower penalty is better. + int bestMaskPattern = -1; + // We try all mask patterns to choose the best one. + for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) + { + MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix); + int penalty = calculateMaskPenalty(matrix); + if (penalty < minPenalty) + { + minPenalty = penalty; + bestMaskPattern = maskPattern; + } + } + return bestMaskPattern; + }catch(Exception e){ + throw new ReaderException(e.Message); + } + } + + // Initialize "qrCode" according to "numInputBytes", "ecLevel", and "mode". On success, modify + // "qrCode". + private static void initQRCode(int numInputBytes, ErrorCorrectionLevel ecLevel, Mode mode, QRCode qrCode) + { + try + { + qrCode.setECLevel(ecLevel); + qrCode.setMode(mode); + + // In the following comments, we use numbers of Version 7-H. + for (int versionNum = 1; versionNum <= 40; versionNum++) { + Version version = Version.getVersionForNumber(versionNum); + // numBytes = 196 + int numBytes = version.getTotalCodewords(); + // getNumECBytes = 130 + Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + int numEcBytes = ecBlocks.getTotalECCodewords(); + // getNumRSBlocks = 5 + int numRSBlocks = ecBlocks.getNumBlocks(); + // getNumDataBytes = 196 - 130 = 66 + int numDataBytes = numBytes - numEcBytes; + // We want to choose the smallest version which can contain data of "numInputBytes" + some + // extra bits for the header (mode info and length info). The header can be three bytes + // (precisely 4 + 16 bits) at most. Hence we do +3 here. + if (numDataBytes >= numInputBytes + 3) { + // Yay, we found the proper rs block info! + qrCode.setVersion(versionNum); + qrCode.setNumTotalBytes(numBytes); + qrCode.setNumDataBytes(numDataBytes); + qrCode.setNumRSBlocks(numRSBlocks); + // getNumECBytes = 196 - 66 = 130 + qrCode.setNumECBytes(numEcBytes); + // matrix width = 21 + 6 * 4 = 45 + qrCode.setMatrixWidth(version.getDimensionForVersion()); + return; + } + } + throw new WriterException("Cannot find proper rs block info (input data too big?)"); + } + catch(Exception e){ + throw new WriterException(e.Message); + } + } + + // Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24). + static void terminateBits(int numDataBytes, BitVector bits){ + int capacity = numDataBytes << 3; + if (bits.size() > capacity) { + throw new WriterException("data bits cannot fit in the QR Code" + bits.size() + " > " + capacity); + } + // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details. + for (int i = 0; i < 4 && bits.size() < capacity; ++i) { + bits.appendBit(0); + } + int numBitsInLastByte = bits.size() % 8; + // If the last byte isn't 8-bit aligned, we'll add padding bits. + if (numBitsInLastByte > 0) { + int numPaddingBits = 8 - numBitsInLastByte; + for (int i = 0; i < numPaddingBits; ++i) { + bits.appendBit(0); + } + } + // Should be 8-bit aligned here. + if (bits.size() % 8 != 0) { + throw new WriterException("Number of bits is not a multiple of 8"); + } + // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24). + int numPaddingBytes = numDataBytes - bits.sizeInBytes(); + for (int i = 0; i < numPaddingBytes; ++i) { + if (i % 2 == 0) { + bits.appendBits(0xec, 8); + } else { + bits.appendBits(0x11, 8); + } + } + if (bits.size() != capacity) { + throw new WriterException("Bits size does not equal capacity"); + } + } + + // Get number of data bytes and number of error correction bytes for block id "blockID". Store + // the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of + // JISX0510:2004 (p.30) + static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes, int numDataBytes, + int numRSBlocks, int blockID, int[] numDataBytesInBlock,int[] numECBytesInBlock) { + if (blockID >= numRSBlocks) { + throw new WriterException("Block ID too large"); + } + // numRsBlocksInGroup2 = 196 % 5 = 1 + int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks; + // numRsBlocksInGroup1 = 5 - 1 = 4 + int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2; + // numTotalBytesInGroup1 = 196 / 5 = 39 + int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks; + // numTotalBytesInGroup2 = 39 + 1 = 40 + int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1; + // numDataBytesInGroup1 = 66 / 5 = 13 + int numDataBytesInGroup1 = numDataBytes / numRSBlocks; + // numDataBytesInGroup2 = 13 + 1 = 14 + int numDataBytesInGroup2 = numDataBytesInGroup1 + 1; + // numEcBytesInGroup1 = 39 - 13 = 26 + int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1; + // numEcBytesInGroup2 = 40 - 14 = 26 + int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2; + // Sanity checks. + // 26 = 26 + if (numEcBytesInGroup1 != numEcBytesInGroup2) { + throw new WriterException("EC bytes mismatch"); + } + // 5 = 4 + 1. + if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) { + throw new WriterException("RS blocks mismatch"); + } + // 196 = (13 + 26) * 4 + (14 + 26) * 1 + if (numTotalBytes != + ((numDataBytesInGroup1 + numEcBytesInGroup1) * + numRsBlocksInGroup1) + + ((numDataBytesInGroup2 + numEcBytesInGroup2) * + numRsBlocksInGroup2)) { + throw new WriterException("Total bytes mismatch"); + } + + if (blockID < numRsBlocksInGroup1) { + numDataBytesInBlock[0] = numDataBytesInGroup1; + numECBytesInBlock[0] = numEcBytesInGroup1; + } else { + numDataBytesInBlock[0] = numDataBytesInGroup2; + numECBytesInBlock[0] = numEcBytesInGroup2; + } + } + + // Interleave "bits" with corresponding error correction bytes. On success, store the result in + // "result" and return true. The interleave rule is complicated. See 8.6 + // of JISX0510:2004 (p.37) for details. + static void interleaveWithECBytes(BitVector bits, int numTotalBytes, + int numDataBytes, int numRSBlocks, BitVector result) { + + // "bits" must have "getNumDataBytes" bytes of data. + if (bits.sizeInBytes() != numDataBytes) { + throw new WriterException("Number of bits and data bytes does not match"); + } + + // Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll + // store the divided data bytes blocks and error correction bytes blocks into "blocks". + int dataBytesOffset = 0; + int maxNumDataBytes = 0; + int maxNumEcBytes = 0; + + // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number. + ArrayList blocks = new ArrayList(numRSBlocks); + + for (int i = 0; i < numRSBlocks; ++i) { + int[] numDataBytesInBlock = new int[1]; + int[] numEcBytesInBlock = new int[1]; + getNumDataBytesAndNumECBytesForBlockID( + numTotalBytes, numDataBytes, numRSBlocks, i, + numDataBytesInBlock, numEcBytesInBlock); + + ByteArray dataBytes = new ByteArray(); + dataBytes.set(bits.getArray(), dataBytesOffset, numDataBytesInBlock[0]); + ByteArray ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]); + blocks.Add(new BlockPair(dataBytes, ecBytes)); + + maxNumDataBytes = Math.Max(maxNumDataBytes, dataBytes.size()); + maxNumEcBytes = Math.Max(maxNumEcBytes, ecBytes.size()); + dataBytesOffset += numDataBytesInBlock[0]; + } + if (numDataBytes != dataBytesOffset) { + throw new WriterException("Data bytes does not match offset"); + } + + // First, place data blocks. + for (int i = 0; i < maxNumDataBytes; ++i) { + for (int j = 0; j < blocks.Count; ++j) { + ByteArray dataBytes = ((BlockPair) blocks[j]).getDataBytes(); + if (i < dataBytes.size()) { + result.appendBits(dataBytes.at(i), 8); + } + } + } + // Then, place error correction blocks. + for (int i = 0; i < maxNumEcBytes; ++i) { + for (int j = 0; j < blocks.Count; ++j) { + ByteArray ecBytes = ((BlockPair) blocks[j]).getErrorCorrectionBytes(); + if (i < ecBytes.size()) { + result.appendBits(ecBytes.at(i), 8); + } + } + } + if (numTotalBytes != result.sizeInBytes()) { // Should be same. + throw new WriterException("Interleaving error: " + numTotalBytes + " and " + result.sizeInBytes() + + " differ."); + } + } + + static ByteArray generateECBytes(ByteArray dataBytes, int numEcBytesInBlock) { + int numDataBytes = dataBytes.size(); + int[] toEncode = new int[numDataBytes + numEcBytesInBlock]; + for (int i = 0; i < numDataBytes; i++) { + toEncode[i] = dataBytes.at(i); + } + new ReedSolomonEncoder(GF256.QR_CODE_FIELD).encode(toEncode, numEcBytesInBlock); + + ByteArray ecBytes = new ByteArray(numEcBytesInBlock); + for (int i = 0; i < numEcBytesInBlock; i++) { + ecBytes.set(i, toEncode[numDataBytes + i]); + } + return ecBytes; + } + + // Append mode info. On success, store the result in "bits" and return true. On error, return + // false. + static void appendModeInfo(Mode mode, BitVector bits) { + bits.appendBits(mode.getBits(), 4); + } + + + // Append length info. On success, store the result in "bits" and return true. On error, return + // false. + static void appendLengthInfo(int numLetters, int version, Mode mode, BitVector bits){ + int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version)); + if (numLetters > ((1 << numBits) - 1)) { + throw new WriterException(numLetters + "is bigger than" + ((1 << numBits) - 1)); + } + bits.appendBits(numLetters, numBits); + } + + // Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits" + // and return true. + static void appendBytes(String content, Mode mode, BitVector bits) { + if (mode.Equals(Mode.NUMERIC)) { + appendNumericBytes(content, bits); + } else if (mode.Equals(Mode.ALPHANUMERIC)) { + appendAlphanumericBytes(content, bits); + } else if (mode.Equals(Mode.BYTE)) { + append8BitBytes(content, bits); + } else if (mode.Equals(Mode.KANJI)) { + appendKanjiBytes(content, bits); + } else { + throw new WriterException("Invalid mode: " + mode); + } + } + + static void appendNumericBytes(String content, BitVector bits) { + int length = content.Length; + int i = 0; + while (i < length) { + int num1 = content[i] - '0'; + if (i + 2 < length) { + // Encode three numeric letters in ten bits. + int num2 = content[i + 1] - '0'; + int num3 = content[i + 2] - '0'; + bits.appendBits(num1 * 100 + num2 * 10 + num3, 10); + i += 3; + } else if (i + 1 < length) { + // Encode two numeric letters in seven bits. + int num2 = content[i + 1] - '0'; + bits.appendBits(num1 * 10 + num2, 7); + i += 2; + } else { + // Encode one numeric letter in four bits. + bits.appendBits(num1, 4); + i++; + } + } + } + + static void appendAlphanumericBytes(String content, BitVector bits) { + int length = content.Length; + int i = 0; + while (i < length) { + int code1 = getAlphanumericCode(content[i]); + if (code1 == -1) { + throw new WriterException(); + } + if (i + 1 < length) { + int code2 = getAlphanumericCode(content[i + 1]); + if (code2 == -1) { + throw new WriterException(); + } + // Encode two alphanumeric letters in 11 bits. + bits.appendBits(code1 * 45 + code2, 11); + i += 2; + } else { + // Encode one alphanumeric letter in six bits. + bits.appendBits(code1, 6); + i++; + } + } + } + + static void append8BitBytes(String content, BitVector bits) { + byte[] bytes; + try { + bytes = System.Text.ASCIIEncoding.ASCII.GetBytes("ISO-8859-1"); + } catch (Exception uee) { + throw new WriterException(uee.ToString()); + } + for (int i = 0; i < bytes.Length; ++i) { + bits.appendBits(bytes[i], 8); + } + } + + static void appendKanjiBytes(String content, BitVector bits) { + byte[] bytes; + try { + bytes=System.Text.ASCIIEncoding.ASCII.GetBytes("Shift_JIS"); + } catch (Exception uee) { + throw new WriterException(uee.ToString()); + } + int length = bytes.Length; + for (int i = 0; i < length; i += 2) { + int byte1 = bytes[i] & 0xFF; + int byte2 = bytes[i + 1] & 0xFF; + int code = (byte1 << 8) | byte2; + int subtracted = -1; + if (code >= 0x8140 && code <= 0x9ffc) { + subtracted = code - 0x8140; + } else if (code >= 0xe040 && code <= 0xebbf) { + subtracted = code - 0xc140; + } + if (subtracted == -1) { + throw new WriterException("Invalid byte sequence"); + } + int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff); + bits.appendBits(encoded, 13); + } + } + + } +} \ No newline at end of file diff --git a/csharp/qrcode/encoder/MaskUtil.cs b/csharp/qrcode/encoder/MaskUtil.cs new file mode 100755 index 000000000..08f3a0453 --- /dev/null +++ b/csharp/qrcode/encoder/MaskUtil.cs @@ -0,0 +1,233 @@ +/* +* 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.Text; +using com.google.zxing; +using com.google.zxing.common; +using com.google.zxing.qrcode.decoder; +using com.google.zxing.qrcode; + +namespace com.google.zxing.qrcode.encoder +{ + + public sealed class MaskUtil + { + private MaskUtil() + { + // do nothing + } + + // Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and + // give penalty to them. Example: 00000 or 11111. + public static int applyMaskPenaltyRule1(ByteMatrix matrix) + { + return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false); + } + + // Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give + // penalty to them. + public static int applyMaskPenaltyRule2(ByteMatrix matrix) + { + int penalty = 0; + sbyte[][] array = matrix.getArray(); + int width = matrix.width(); + int height = matrix.height(); + for (int y = 0; y < height - 1; ++y) + { + for (int x = 0; x < width - 1; ++x) + { + int value = array[y][x]; + if (value == array[y][x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) + { + penalty += 3; + } + } + } + return penalty; + } + + // Apply mask penalty rule 3 and return the penalty. Find consecutive cells of 00001011101 or + // 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give + // penalties twice (i.e. 40 * 2). + public static int applyMaskPenaltyRule3(ByteMatrix matrix) + { + int penalty = 0; + sbyte[][] array = matrix.getArray(); + int width = matrix.width(); + int height = matrix.height(); + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + // Tried to simplify following conditions but failed. + if (x + 6 < width && + array[y][x] == 1 && + array[y][x + 1] == 0 && + array[y][x + 2] == 1 && + array[y][x + 3] == 1 && + array[y][x + 4] == 1 && + array[y][x + 5] == 0 && + array[y][x + 6] == 1 && + ((x + 10 < width && + array[y][x + 7] == 0 && + array[y][x + 8] == 0 && + array[y][x + 9] == 0 && + array[y][x + 10] == 0) || + (x - 4 >= 0 && + array[y][x - 1] == 0 && + array[y][x - 2] == 0 && + array[y][x - 3] == 0 && + array[y][x - 4] == 0))) + { + penalty += 40; + } + if (y + 6 < height && + array[y][x] == 1 && + array[y + 1][x] == 0 && + array[y + 2][x] == 1 && + array[y + 3][x] == 1 && + array[y + 4][x] == 1 && + array[y + 5][x] == 0 && + array[y + 6][x] == 1 && + ((y + 10 < height && + array[y + 7][x] == 0 && + array[y + 8][x] == 0 && + array[y + 9][x] == 0 && + array[y + 10][x] == 0) || + (y - 4 >= 0 && + array[y - 1][x] == 0 && + array[y - 2][x] == 0 && + array[y - 3][x] == 0 && + array[y - 4][x] == 0))) + { + penalty += 40; + } + } + } + return penalty; + } + + // Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give + // penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance. Examples: + // - 0% => 100 + // - 40% => 20 + // - 45% => 10 + // - 50% => 0 + // - 55% => 10 + // - 55% => 20 + // - 100% => 100 + public static int applyMaskPenaltyRule4(ByteMatrix matrix) + { + int numDarkCells = 0; + sbyte[][] array = matrix.getArray(); + int width = matrix.width(); + int height = matrix.height(); + for (int y = 0; y < height; ++y) + { + for (int x = 0; x < width; ++x) + { + if (array[y][x] == 1) + { + numDarkCells += 1; + } + } + } + int numTotalCells = matrix.height() * matrix.width(); + double darkRatio = (double)numDarkCells / numTotalCells; + return Math.Abs((int)(darkRatio * 100 - 50)) / 5 * 10; + } + + // Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask + // pattern conditions. + public static int getDataMaskBit(int maskPattern, int x, int y) + { + if (!QRCode.isValidMaskPattern(maskPattern)) + { + throw new ArgumentException("Invalid mask pattern"); + } + switch (maskPattern) + { + case 0: + return ((y + x) % 2 == 0) ? 1 : 0; + case 1: + return (y % 2 == 0) ? 1 : 0; + case 2: + return (x % 3 == 0) ? 1 : 0; + case 3: + return ((y + x) % 3 == 0) ? 1 : 0; + case 4: + return (((y / 2) + (x / 3)) % 2 == 0) ? 1 : 0; + case 5: + return (((y * x) % 2) + ((y * x) % 3) == 0) ? 1 : 0; + case 6: + return ((((y * x) % 2) + ((y * x) % 3)) % 2 == 0) ? 1 : 0; + case 7: + return ((((y * x) % 3) + ((y + x) % 2)) % 2 == 0) ? 1 : 0; + } + throw new ArgumentException("invalid mask pattern: " + maskPattern); + } + + // Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both + // vertical and horizontal orders respectively. + private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, bool isHorizontal) + { + int penalty = 0; + int numSameBitCells = 0; + int prevBit = -1; + // Horizontal mode: + // for (int i = 0; i < matrix.height(); ++i) { + // for (int j = 0; j < matrix.width(); ++j) { + // int bit = matrix.get(i, j); + // Vertical mode: + // for (int i = 0; i < matrix.width(); ++i) { + // for (int j = 0; j < matrix.height(); ++j) { + // int bit = matrix.get(j, i); + int iLimit = isHorizontal ? matrix.height() : matrix.width(); + int jLimit = isHorizontal ? matrix.width() : matrix.height(); + sbyte[][] array = matrix.getArray(); + for (int i = 0; i < iLimit; ++i) + { + for (int j = 0; j < jLimit; ++j) + { + int bit = isHorizontal ? array[i][j] : array[j][i]; + if (bit == prevBit) + { + numSameBitCells += 1; + // Found five repetitive cells with the same color (bit). + // We'll give penalty of 3. + if (numSameBitCells == 5) + { + penalty += 3; + } + else if (numSameBitCells > 5) + { + // After five repetitive cells, we'll add the penalty one + // by one. + penalty += 1; + } + } + else + { + numSameBitCells = 1; // Include the cell itself. + prevBit = bit; + } + } + numSameBitCells = 0; // Clear at each row/column. + } + return penalty; + } + } +} \ No newline at end of file diff --git a/csharp/qrcode/encoder/MatrixUtil.cs b/csharp/qrcode/encoder/MatrixUtil.cs new file mode 100755 index 000000000..eb763d19b --- /dev/null +++ b/csharp/qrcode/encoder/MatrixUtil.cs @@ -0,0 +1,531 @@ +/* +* 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.Text; +using com.google.zxing; +using com.google.zxing.common; +using com.google.zxing.qrcode.decoder; +using com.google.zxing.qrcode; + +namespace com.google.zxing.qrcode.encoder +{ + public sealed class MatrixUtil + { + private MatrixUtil() { + // do nothing + } + + private static int[][] POSITION_DETECTION_PATTERN = new int[][]{ + new int[]{1, 1, 1, 1, 1, 1, 1}, + new int[]{1, 0, 0, 0, 0, 0, 1}, + new int[]{1, 0, 1, 1, 1, 0, 1}, + new int[]{1, 0, 1, 1, 1, 0, 1}, + new int[]{1, 0, 1, 1, 1, 0, 1}, + new int[]{1, 0, 0, 0, 0, 0, 1}, + new int[]{1, 1, 1, 1, 1, 1, 1}, + }; + + private static int[][] HORIZONTAL_SEPARATION_PATTERN = new int[][]{ + new int[]{0, 0, 0, 0, 0, 0, 0, 0}, + }; + + private static int[][] VERTICAL_SEPARATION_PATTERN = new int[][]{ + new int[]{0}, new int[]{0}, new int[]{0}, new int[]{0}, new int[]{0}, new int[]{0}, new int[]{0}, + }; + + private static int[][] POSITION_ADJUSTMENT_PATTERN = new int[][]{ + new int[]{1, 1, 1, 1, 1}, + new int[]{1, 0, 0, 0, 1}, + new int[]{1, 0, 1, 0, 1}, + new int[]{1, 0, 0, 0, 1}, + new int[]{1, 1, 1, 1, 1}, + }; + + // From Appendix E. Table 1, JIS0510X:2004 (p 71). The table was double-checked by komatsu. + private static int[][] POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = new int[][]{ + new int[]{-1, -1, -1, -1, -1, -1, -1}, // Version 1 + new int[]{ 6, 18, -1, -1, -1, -1, -1}, // Version 2 + new int[]{ 6, 22, -1, -1, -1, -1, -1}, // Version 3 + new int[]{ 6, 26, -1, -1, -1, -1, -1}, // Version 4 + new int[]{ 6, 30, -1, -1, -1, -1, -1}, // Version 5 + new int[]{ 6, 34, -1, -1, -1, -1, -1}, // Version 6 + new int[]{ 6, 22, 38, -1, -1, -1, -1}, // Version 7 + new int[]{ 6, 24, 42, -1, -1, -1, -1}, // Version 8 + new int[]{ 6, 26, 46, -1, -1, -1, -1}, // Version 9 + new int[]{ 6, 28, 50, -1, -1, -1, -1}, // Version 10 + new int[]{ 6, 30, 54, -1, -1, -1, -1}, // Version 11 + new int[]{ 6, 32, 58, -1, -1, -1, -1}, // Version 12 + new int[]{ 6, 34, 62, -1, -1, -1, -1}, // Version 13 + new int[]{ 6, 26, 46, 66, -1, -1, -1}, // Version 14 + new int[]{ 6, 26, 48, 70, -1, -1, -1}, // Version 15 + new int[]{ 6, 26, 50, 74, -1, -1, -1}, // Version 16 + new int[]{ 6, 30, 54, 78, -1, -1, -1}, // Version 17 + new int[]{ 6, 30, 56, 82, -1, -1, -1}, // Version 18 + new int[]{ 6, 30, 58, 86, -1, -1, -1}, // Version 19 + new int[]{ 6, 34, 62, 90, -1, -1, -1}, // Version 20 + new int[]{ 6, 28, 50, 72, 94, -1, -1}, // Version 21 + new int[]{ 6, 26, 50, 74, 98, -1, -1}, // Version 22 + new int[]{ 6, 30, 54, 78, 102, -1, -1}, // Version 23 + new int[]{ 6, 28, 54, 80, 106, -1, -1}, // Version 24 + new int[]{ 6, 32, 58, 84, 110, -1, -1}, // Version 25 + new int[]{ 6, 30, 58, 86, 114, -1, -1}, // Version 26 + new int[]{ 6, 34, 62, 90, 118, -1, -1}, // Version 27 + new int[]{ 6, 26, 50, 74, 98, 122, -1}, // Version 28 + new int[]{ 6, 30, 54, 78, 102, 126, -1}, // Version 29 + new int[]{ 6, 26, 52, 78, 104, 130, -1}, // Version 30 + new int[]{ 6, 30, 56, 82, 108, 134, -1}, // Version 31 + new int[]{ 6, 34, 60, 86, 112, 138, -1}, // Version 32 + new int[]{ 6, 30, 58, 86, 114, 142, -1}, // Version 33 + new int[]{ 6, 34, 62, 90, 118, 146, -1}, // Version 34 + new int[]{ 6, 30, 54, 78, 102, 126, 150}, // Version 35 + new int[]{ 6, 24, 50, 76, 102, 128, 154}, // Version 36 + new int[]{ 6, 28, 54, 80, 106, 132, 158}, // Version 37 + new int[]{ 6, 32, 58, 84, 110, 136, 162}, // Version 38 + new int[]{ 6, 26, 54, 82, 110, 138, 166}, // Version 39 + new int[]{ 6, 30, 58, 86, 114, 142, 170}, // Version 40 + }; + + // Type info cells at the left top corner. + private static int[][] TYPE_INFO_COORDINATES = new int[][]{ + new int[]{8, 0}, + new int[]{8, 1}, + new int[]{8, 2}, + new int[]{8, 3}, + new int[]{8, 4}, + new int[]{8, 5}, + new int[]{8, 7}, + new int[]{8, 8}, + new int[]{7, 8}, + new int[]{5, 8}, + new int[]{4, 8}, + new int[]{3, 8}, + new int[]{2, 8}, + new int[]{1, 8}, + new int[]{0, 8}, + }; + + // From Appendix D in JISX0510:2004 (p. 67) + private static int VERSION_INFO_POLY = 0x1f25; // 1 1111 0010 0101 + + // From Appendix C in JISX0510:2004 (p.65). + private static int TYPE_INFO_POLY = 0x537; + private static int TYPE_INFO_MASK_PATTERN = 0x5412; + + // Set all cells to -1. -1 means that the cell is empty (not set yet). + // + // JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding + // with the ByteMatrix initialized all to zero. + public static void clearMatrix(ByteMatrix matrix) { + matrix.clear((sbyte)(-1)); + } + + // Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On + // success, store the result in "matrix" and return true. + public static void buildMatrix(BitVector dataBits, ErrorCorrectionLevel ecLevel, int version,int maskPattern, ByteMatrix matrix) { + try{ + clearMatrix(matrix); + embedBasicPatterns(version, matrix); + // Type information appear with any version. + embedTypeInfo(ecLevel, maskPattern, matrix); + // Version info appear if version >= 7. + maybeEmbedVersionInfo(version, matrix); + // Data should be embedded at end. + embedDataBits(dataBits, maskPattern, matrix); + }catch(Exception e){ + throw new WriterException(e.Message); + } + + } + + // Embed basic patterns. On success, modify the matrix and return true. + // The basic patterns are: + // - Position detection patterns + // - Timing patterns + // - Dark dot at the left bottom corner + // - Position adjustment patterns, if need be + public static void embedBasicPatterns(int version, ByteMatrix matrix){ + try + { + // Let's get started with embedding big squares at corners. + embedPositionDetectionPatternsAndSeparators(matrix); + // Then, embed the dark dot at the left bottom corner. + embedDarkDotAtLeftBottomCorner(matrix); + + // Position adjustment patterns appear if version >= 2. + maybeEmbedPositionAdjustmentPatterns(version, matrix); + // Timing patterns should be embedded after position adj. patterns. + embedTimingPatterns(matrix); + }catch(Exception e){ + throw new WriterException (e.Message); + } + } + + // Embed type information. On success, modify the matrix. + public static void embedTypeInfo(ErrorCorrectionLevel ecLevel, int maskPattern, ByteMatrix matrix) + { + BitVector typeInfoBits = new BitVector(); + makeTypeInfoBits(ecLevel, maskPattern, typeInfoBits); + + for (int i = 0; i < typeInfoBits.size(); ++i) { + // Place bits in LSB to MSB order. LSB (least significant bit) is the last value in + // "typeInfoBits". + int bit = typeInfoBits.at(typeInfoBits.size() - 1 - i); + + // Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46). + int x1 = TYPE_INFO_COORDINATES[i][0]; + int y1 = TYPE_INFO_COORDINATES[i][1]; + matrix.set(y1, x1, bit); + + if (i < 8) { + // Right top corner. + int x2 = matrix.width() - i - 1; + int y2 = 8; + matrix.set(y2, x2, bit); + } else { + // Left bottom corner. + int x2 = 8; + int y2 = matrix.height() - 7 + (i - 8); + matrix.set(y2, x2, bit); + } + } + } + + // Embed version information if need be. On success, modify the matrix and return true. + // See 8.10 of JISX0510:2004 (p.47) for how to embed version information. + public static void maybeEmbedVersionInfo(int version, ByteMatrix matrix){ + if (version < 7) { // Version info is necessary if version >= 7. + return; // Don't need version info. + } + BitVector versionInfoBits = new BitVector(); + makeVersionInfoBits(version, versionInfoBits); + + int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0. + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 3; ++j) { + // Place bits in LSB (least significant bit) to MSB order. + int bit = versionInfoBits.at(bitIndex); + bitIndex--; + // Left bottom corner. + matrix.set(matrix.height() - 11 + j, i, bit); + // Right bottom corner. + matrix.set(i, matrix.height() - 11 + j, bit); + } + } + } + + // Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true. + // For debugging purposes, it skips masking process if "getMaskPattern" is -1. + // See 8.7 of JISX0510:2004 (p.38) for how to embed data bits. + public static void embedDataBits(BitVector dataBits, int maskPattern, ByteMatrix matrix) + { + int bitIndex = 0; + int direction = -1; + // Start from the right bottom cell. + int x = matrix.width() - 1; + int y = matrix.height() - 1; + while (x > 0) { + // Skip the vertical timing pattern. + if (x == 6) { + x -= 1; + } + while (y >= 0 && y < matrix.height()) { + for (int i = 0; i < 2; ++i) { + int xx = x - i; + // Skip the cell if it's not empty. + if (!isEmpty(matrix.get(y, xx))) { + continue; + } + int bit; + if (bitIndex < dataBits.size()) { + bit = dataBits.at(bitIndex); + ++bitIndex; + } else { + // Padding bit. If there is no bit left, we'll fill the left cells with 0, as described + // in 8.4.9 of JISX0510:2004 (p. 24). + bit = 0; + } + + // Skip masking if mask_pattern is -1. + if (maskPattern != -1) { + int mask = MaskUtil.getDataMaskBit(maskPattern, xx, y); + bit ^= mask; + } + matrix.set(y, xx, bit); + } + y += direction; + } + direction = -direction; // Reverse the direction. + y += direction; + x -= 2; // Move to the left. + } + // All bits should be consumed. + if (bitIndex != dataBits.size()) { + throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.size()); + } + } + + // Return the position of the most significant bit set (to one) in the "value". The most + // significant bit is position 32. If there is no bit set, return 0. Examples: + // - findMSBSet(0) => 0 + // - findMSBSet(1) => 1 + // - findMSBSet(255) => 8 + public static int findMSBSet(int value) { + int numDigits = 0; + while (value != 0) { + value >>= 1; + ++numDigits; + } + return numDigits; + } + + // Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". The BCH + // code is used for encoding type information and version information. + // Example: Calculation of version information of 7. + // f(x) is created from 7. + // - 7 = 000111 in 6 bits + // - f(x) = x^2 + x^2 + x^1 + // g(x) is given by the standard (p. 67) + // - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1 + // Multiply f(x) by x^(18 - 6) + // - f'(x) = f(x) * x^(18 - 6) + // - f'(x) = x^14 + x^13 + x^12 + // Calculate the remainder of f'(x) / g(x) + // x^2 + // __________________________________________________ + // g(x) )x^14 + x^13 + x^12 + // x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2 + // -------------------------------------------------- + // x^11 + x^10 + x^7 + x^4 + x^2 + // + // The remainder is x^11 + x^10 + x^7 + x^4 + x^2 + // Encode it in binary: 110010010100 + // The return value is 0xc94 (1100 1001 0100) + // + // Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit + // operations. We don't care if cofficients are positive or negative. + public static int calculateBCHCode(int value, int poly) { + // If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1 + // from 13 to make it 12. + int msbSetInPoly = findMSBSet(poly); + value <<= msbSetInPoly - 1; + // Do the division business using exclusive-or operations. + while (findMSBSet(value) >= msbSetInPoly) { + value ^= poly << (findMSBSet(value) - msbSetInPoly); + } + // Now the "value" is the remainder (i.e. the BCH code) + return value; + } + + // Make bit vector of type information. On success, store the result in "bits" and return true. + // Encode error correction level and mask pattern. See 8.9 of + // JISX0510:2004 (p.45) for details. + public static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitVector bits) + { + if (!QRCode.isValidMaskPattern(maskPattern)) { + throw new WriterException("Invalid mask pattern"); + } + int typeInfo = (ecLevel.getBits() << 3) | maskPattern; + bits.appendBits(typeInfo, 5); + + int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY); + bits.appendBits(bchCode, 10); + + BitVector maskBits = new BitVector(); + maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15); + bits.xor(maskBits); + + if (bits.size() != 15) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.size()); + } + } + + // Make bit vector of version information. On success, store the result in "bits" and return true. + // See 8.10 of JISX0510:2004 (p.45) for details. + public static void makeVersionInfoBits(int version, BitVector bits){ + bits.appendBits(version, 6); + int bchCode = calculateBCHCode(version, VERSION_INFO_POLY); + bits.appendBits(bchCode, 12); + + if (bits.size() != 18) { // Just in case. + throw new WriterException("should not happen but we got: " + bits.size()); + } + } + + // Check if "value" is empty. + private static bool isEmpty(int value) { + return value == -1; + } + + // Check if "value" is valid. + private static bool isValidValue(int value) { + return (value == -1 || // Empty. + value == 0 || // Light (white). + value == 1); // Dark (black). + } + + private static void embedTimingPatterns(ByteMatrix matrix) { + // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical + // separation patterns (size 1). Thus, 8 = 7 + 1. + for (int i = 8; i < matrix.width() - 8; ++i) { + int bit = (i + 1) % 2; + // Horizontal line. + if (!isValidValue(matrix.get(6, i))) { + throw new WriterException(); + } + if (isEmpty(matrix.get(6, i))) { + matrix.set(6, i, bit); + } + // Vertical line. + if (!isValidValue(matrix.get(i, 6))) { + throw new WriterException(); + } + if (isEmpty(matrix.get(i, 6))) { + matrix.set(i, 6, bit); + } + } + } + + // Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46) + private static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix){ + if (matrix.get(matrix.height() - 8, 8) == 0) { + throw new WriterException(); + } + matrix.set(matrix.height() - 8, 8, 1); + } + + private static void embedHorizontalSeparationPattern(int xStart, int yStart,ByteMatrix matrix) { + // We know the width and height. + if (HORIZONTAL_SEPARATION_PATTERN[0].Length != 8 || HORIZONTAL_SEPARATION_PATTERN.Length != 1) { + throw new WriterException("Bad horizontal separation pattern"); + } + for (int x = 0; x < 8; ++x) { + if (!isEmpty(matrix.get(yStart, xStart + x))) { + throw new WriterException(); + } + matrix.set(yStart, xStart + x, HORIZONTAL_SEPARATION_PATTERN[0][x]); + } + } + + private static void embedVerticalSeparationPattern(int xStart, int yStart,ByteMatrix matrix){ + // We know the width and height. + if (VERTICAL_SEPARATION_PATTERN[0].Length != 1 || VERTICAL_SEPARATION_PATTERN.Length != 7) { + throw new WriterException("Bad vertical separation pattern"); + } + for (int y = 0; y < 7; ++y) { + if (!isEmpty(matrix.get(yStart + y, xStart))) { + throw new WriterException(); + } + matrix.set(yStart + y, xStart, VERTICAL_SEPARATION_PATTERN[y][0]); + } + } + + // Note that we cannot unify the function with embedPositionDetectionPattern() despite they are + // almost identical, since we cannot write a function that takes 2D arrays in different sizes in + // C/C++. We should live with the fact. + private static void embedPositionAdjustmentPattern(int xStart, int yStart,ByteMatrix matrix){ + // We know the width and height. + if (POSITION_ADJUSTMENT_PATTERN[0].Length != 5 || POSITION_ADJUSTMENT_PATTERN.Length != 5) { + throw new WriterException("Bad position adjustment"); + } + for (int y = 0; y < 5; ++y) { + for (int x = 0; x < 5; ++x) { + if (!isEmpty(matrix.get(yStart + y, xStart + x))) { + throw new WriterException(); + } + matrix.set(yStart + y, xStart + x, POSITION_ADJUSTMENT_PATTERN[y][x]); + } + } + } + + private static void embedPositionDetectionPattern(int xStart, int yStart,ByteMatrix matrix){ + // We know the width and height. + if (POSITION_DETECTION_PATTERN[0].Length != 7 || POSITION_DETECTION_PATTERN.Length != 7) { + throw new WriterException("Bad position detection pattern"); + } + for (int y = 0; y < 7; ++y) { + for (int x = 0; x < 7; ++x) { + if (!isEmpty(matrix.get(yStart + y, xStart + x))) { + throw new WriterException(); + } + matrix.set(yStart + y, xStart + x, POSITION_DETECTION_PATTERN[y][x]); + } + } + } + + // Embed position detection patterns and surrounding vertical/horizontal separators. + private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) { + // Embed three big squares at corners. + int pdpWidth = POSITION_DETECTION_PATTERN[0].Length; + // Left top corner. + embedPositionDetectionPattern(0, 0, matrix); + // Right top corner. + embedPositionDetectionPattern(matrix.width() - pdpWidth, 0, matrix); + // Left bottom corner. + embedPositionDetectionPattern(0, matrix.width() - pdpWidth, matrix); + + // Embed horizontal separation patterns around the squares. + int hspWidth = HORIZONTAL_SEPARATION_PATTERN[0].Length; + // Left top corner. + embedHorizontalSeparationPattern(0, hspWidth - 1, matrix); + // Right top corner. + embedHorizontalSeparationPattern(matrix.width() - hspWidth, + hspWidth - 1, matrix); + // Left bottom corner. + embedHorizontalSeparationPattern(0, matrix.width() - hspWidth, matrix); + + // Embed vertical separation patterns around the squares. + int vspSize = VERTICAL_SEPARATION_PATTERN.Length; + // Left top corner. + embedVerticalSeparationPattern(vspSize, 0, matrix); + // Right top corner. + embedVerticalSeparationPattern(matrix.height() - vspSize - 1, 0, matrix); + // Left bottom corner. + embedVerticalSeparationPattern(vspSize, matrix.height() - vspSize, + matrix); + } + + // Embed position adjustment patterns if need be. + private static void maybeEmbedPositionAdjustmentPatterns(int version, ByteMatrix matrix) + { + if (version < 2) { // The patterns appear if version >= 2 + return; + } + int index = version - 1; + int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index]; + int numCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index].Length; + for (int i = 0; i < numCoordinates; ++i) { + for (int j = 0; j < numCoordinates; ++j) { + int y = coordinates[i]; + int x = coordinates[j]; + if (x == -1 || y == -1) { + continue; + } + // If the cell is unset, we embed the position adjustment pattern here. + if (isEmpty(matrix.get(y, x))) { + // -2 is necessary since the x/y coordinates point to the center of the pattern, not the + // left top corner. + embedPositionAdjustmentPattern(x - 2, y - 2, matrix); + } + } + } + } + + + + } + + +} \ No newline at end of file diff --git a/csharp/qrcode/encoder/QRCode.cs b/csharp/qrcode/encoder/QRCode.cs new file mode 100755 index 000000000..b4292c99f --- /dev/null +++ b/csharp/qrcode/encoder/QRCode.cs @@ -0,0 +1,242 @@ +/* +* 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.Text; +using com.google.zxing; +using com.google.zxing.common; +using com.google.zxing.qrcode.decoder; +using com.google.zxing.qrcode; + +namespace com.google.zxing.qrcode.encoder +{ + + public sealed class QRCode + { + + public static int NUM_MASK_PATTERNS = 8; + + private Mode mode; + private ErrorCorrectionLevel ecLevel; + private int version; + private int matrixWidth; + private int maskPattern; + private int numTotalBytes; + private int numDataBytes; + private int numECBytes; + private int numRSBlocks; + private ByteMatrix matrix; + + public QRCode() { + mode = null; + ecLevel = null; + version = -1; + matrixWidth = -1; + maskPattern = -1; + numTotalBytes = -1; + numDataBytes = -1; + numECBytes = -1; + numRSBlocks = -1; + matrix = null; + } + + // Mode of the QR Code. + public Mode getMode() { + return mode; + } + + // Error correction level of the QR Code. + public ErrorCorrectionLevel getECLevel() { + return ecLevel; + } + + // Version of the QR Code. The bigger size, the bigger version. + public int getVersion() { + return version; + } + + // ByteMatrix width of the QR Code. + public int getMatrixWidth() { + return matrixWidth; + } + + // Mask pattern of the QR Code. + public int getMaskPattern() { + return maskPattern; + } + + // Number of total bytes in the QR Code. + public int getNumTotalBytes() { + return numTotalBytes; + } + + // Number of data bytes in the QR Code. + public int getNumDataBytes() { + return numDataBytes; + } + + // Number of error correction bytes in the QR Code. + public int getNumECBytes() { + return numECBytes; + } + + // Number of Reedsolomon blocks in the QR Code. + public int getNumRSBlocks() { + return numRSBlocks; + } + + // ByteMatrix data of the QR Code. + public ByteMatrix getMatrix() { + return matrix; + } + + + // Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They + // call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell. + public int at(int x, int y) { + // The value must be zero or one. + int value = matrix.get(y, x); + if (!(value == 0 || value == 1)) { + // this is really like an assert... not sure what better exception to use? + throw new Exception("Bad value"); + } + return value; + } + + // Checks all the member variables are set properly. Returns true on success. Otherwise, returns + // false. + public bool isValid() { + return + // First check if all version are not uninitialized. + mode != null && + ecLevel != null && + version != -1 && + matrixWidth != -1 && + maskPattern != -1 && + numTotalBytes != -1 && + numDataBytes != -1 && + numECBytes != -1 && + numRSBlocks != -1 && + // Then check them in other ways.. + isValidMaskPattern(maskPattern) && + numTotalBytes == numDataBytes + numECBytes && + // ByteMatrix stuff. + matrix != null && + matrixWidth == matrix.width() && + // See 7.3.1 of JISX0510:2004 (p.5). + matrix.width() == matrix.height(); // Must be square. + } + + // Return debug String. + public String toString() { + StringBuilder result = new StringBuilder(200); + result.Append("<<\n"); + result.Append(" mode: "); + result.Append(mode); + result.Append("\n ecLevel: "); + result.Append(ecLevel); + result.Append("\n version: "); + result.Append(version); + result.Append("\n matrixWidth: "); + result.Append(matrixWidth); + result.Append("\n maskPattern: "); + result.Append(maskPattern); + result.Append("\n numTotalBytes: "); + result.Append(numTotalBytes); + result.Append("\n numDataBytes: "); + result.Append(numDataBytes); + result.Append("\n numECBytes: "); + result.Append(numECBytes); + result.Append("\n numRSBlocks: "); + result.Append(numRSBlocks); + if (matrix == null) { + result.Append("\n matrix: null\n"); + } else { + result.Append("\n matrix:\n"); + result.Append(matrix.toString()); + } + result.Append(">>\n"); + return result.ToString(); + } + + public void setMode(Mode value) { + mode = value; + } + + public void setECLevel(ErrorCorrectionLevel value) { + ecLevel = value; + } + + public void setVersion(int value) { + version = value; + } + + public void setMatrixWidth(int value) { + matrixWidth = value; + } + + public void setMaskPattern(int value) { + maskPattern = value; + } + + public void setNumTotalBytes(int value) { + numTotalBytes = value; + } + + public void setNumDataBytes(int value) { + numDataBytes = value; + } + + public void setNumECBytes(int value) { + numECBytes = value; + } + + public void setNumRSBlocks(int value) { + numRSBlocks = value; + } + + // This takes ownership of the 2D array. + public void setMatrix(ByteMatrix value) { + matrix = value; + } + + // Check if "mask_pattern" is valid. + public static bool isValidMaskPattern(int maskPattern) { + return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS; + } + + // Return true if the all values in the matrix are binary numbers. + // + // JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in + // production. I'm leaving it because it may be useful for testing. It should be removed entirely + // if ByteMatrix is changed never to contain a -1. + /* + private static boolean EverythingIsBinary(final ByteMatrix matrix) { + for (int y = 0; y < matrix.height(); ++y) { + for (int x = 0; x < matrix.width(); ++x) { + int value = matrix.get(y, x); + if (!(value == 0 || value == 1)) { + // Found non zero/one value. + return false; + } + } + } + return true; + } + */ + + } + +} \ No newline at end of file diff --git a/csharp/zxing.csproj b/csharp/zxing.csproj new file mode 100755 index 000000000..364b718b6 --- /dev/null +++ b/csharp/zxing.csproj @@ -0,0 +1,135 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {22174889-87F2-4843-A5CE-99A4847D24EE} + Library + Properties + com.google.zxing + com.google.zxing + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + + 3.5 + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/zxing.sln b/csharp/zxing.sln new file mode 100755 index 000000000..350ef5ffe --- /dev/null +++ b/csharp/zxing.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual C# Express 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zxing", "zxing.csproj", "{22174889-87F2-4843-A5CE-99A4847D24EE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {22174889-87F2-4843-A5CE-99A4847D24EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22174889-87F2-4843-A5CE-99A4847D24EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22174889-87F2-4843-A5CE-99A4847D24EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22174889-87F2-4843-A5CE-99A4847D24EE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal