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 different methods of sampling an imagine to estimate a black point. 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. Encapsulates a generic black-and-white bitmap -- a collection of pixels in two dimensions.
+ /// This unifies many possible representations, like AWT's Y = 0.299R + 0.587G + 0.114B
+ ///
+ /// where R, G, and B are values in [0,1].
+ /// BufferedImage
.
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 +{ + + ///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
+{
+
+ /// 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; + + ///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; + + ///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. + ///
+ /// + ///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; + + ///Comparator
since it is not available in
+ /// CLDC 1.1 / MIDP 2.0.
+ /// 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;
+ /// 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. 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. 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. 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 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. 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.int
+ /// for convenience and speed (but at the cost of memory).
+ /// Only the bottom 8 bits are really used.
+ ///
+ ///
+ ///
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.
+ /// + ///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.
+ /// + ///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.
+ /// + ///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.
+ /// + ///Implementations of this method reverse the data masking process applied to a QR Code and + /// make its bits ready to read.
+ /// + ///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
+ /// + ///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 @@ + +