From 748d84809777074551482b7c0568a2c824f87674 Mon Sep 17 00:00:00 2001 From: mforez Date: Wed, 23 Dec 2020 19:57:24 +0100 Subject: [PATCH] Added csharp version --- csharp/QrCodeGenerator.sln | 31 + csharp/QrCodeGenerator/BitBuffer.cs | 94 ++ csharp/QrCodeGenerator/BitSet/BitSet.cs | 701 +++++++++++++++ csharp/QrCodeGenerator/BitSet/ObjectCopier.cs | 44 + .../CustomExceptions/DataTooLongException.cs | 38 + csharp/QrCodeGenerator/ErrorCorrection.cs | 36 + csharp/QrCodeGenerator/Mode.cs | 46 + csharp/QrCodeGenerator/QrCode.cs | 822 ++++++++++++++++++ csharp/QrCodeGenerator/QrCodeGenerator.csproj | 12 + csharp/QrCodeGenerator/QrSegment.cs | 240 +++++ 10 files changed, 2064 insertions(+) create mode 100644 csharp/QrCodeGenerator.sln create mode 100644 csharp/QrCodeGenerator/BitBuffer.cs create mode 100644 csharp/QrCodeGenerator/BitSet/BitSet.cs create mode 100644 csharp/QrCodeGenerator/BitSet/ObjectCopier.cs create mode 100644 csharp/QrCodeGenerator/CustomExceptions/DataTooLongException.cs create mode 100644 csharp/QrCodeGenerator/ErrorCorrection.cs create mode 100644 csharp/QrCodeGenerator/Mode.cs create mode 100644 csharp/QrCodeGenerator/QrCode.cs create mode 100644 csharp/QrCodeGenerator/QrCodeGenerator.csproj create mode 100644 csharp/QrCodeGenerator/QrSegment.cs diff --git a/csharp/QrCodeGenerator.sln b/csharp/QrCodeGenerator.sln new file mode 100644 index 0000000..584c1f1 --- /dev/null +++ b/csharp/QrCodeGenerator.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30621.155 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QrCodeGenerator", "QrCodeGenerator\QrCodeGenerator.csproj", "{59D32E1D-4389-420F-9E3B-89511C2D00E7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{1DC53F75-6392-4ED6-A22C-D9A1B5181252}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59D32E1D-4389-420F-9E3B-89511C2D00E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59D32E1D-4389-420F-9E3B-89511C2D00E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59D32E1D-4389-420F-9E3B-89511C2D00E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59D32E1D-4389-420F-9E3B-89511C2D00E7}.Release|Any CPU.Build.0 = Release|Any CPU + {1DC53F75-6392-4ED6-A22C-D9A1B5181252}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DC53F75-6392-4ED6-A22C-D9A1B5181252}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DC53F75-6392-4ED6-A22C-D9A1B5181252}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DC53F75-6392-4ED6-A22C-D9A1B5181252}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9F1CC914-C13A-4C43-B585-E43BFBD19133} + EndGlobalSection +EndGlobal diff --git a/csharp/QrCodeGenerator/BitBuffer.cs b/csharp/QrCodeGenerator/BitBuffer.cs new file mode 100644 index 0000000..d72679a --- /dev/null +++ b/csharp/QrCodeGenerator/BitBuffer.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections; +using System.Diagnostics; +using Util; + +namespace QrCodeGenerator +{ + /// + /// An appendable sequence of bits (0s and 1s). Mainly used by . + /// + public class BitBuffer + { + private BitSet Data; + private int BitLength; + + /// + /// Constructs an empty bit buffer (length 0). + /// + public BitBuffer() + { + Data = new BitSet(); + BitLength = 0; + } + + /// + /// Returns the length of this sequence, which is a non-negative value. + /// + /// The length of this sequence + public int GetBitLength() + { + return BitLength; + } + + /// + /// Returns the bit at the specified index, yielding 0 or 1. + /// + /// The index to get the bit at + /// The bit at the specified index + /// If index < 0 or index ≥ bitLength + public int GetBit(int index) + { + if (index < 0 || index >= BitLength) + throw new IndexOutOfRangeException(); + return Data.Get(index) ? 1 : 0; + } + + /// + /// Appends the specified number of low-order bits of the specified value to this + /// buffer. Requires 0 ≤ len ≤ 31 and 0 ≤ val < 2len. + /// + /// The value to append + /// The number of low-order bits in the value to take + /// If the value or number of bits is out of range + /// If appending the data + public void AppendBits(int value, int length) + { + if (length < 0 || length > 31 || value >> length != 0) + throw new ArgumentException("Value out of range"); + if (int.MaxValue - BitLength < length) + throw new InvalidOperationException("Maximum length reached"); + + for (int i = length - 1; i >= 0; i--, BitLength++) + Data.Set(BitLength, QrCode.GetBit(value, i)); + } + + /// + /// Appends the content of the specified bit buffer to this buffer. + /// + /// The bit buffer whose data to append (not null) + /// If the bit buffer is null + /// If appending the data would make bitLength exceed Integer.MaxValue + public void AppendData(BitBuffer bitBuffer) + { + if (bitBuffer == null) + throw new ArgumentNullException("BitBuffer is null!"); + if (int.MaxValue - BitLength < bitBuffer.GetBitLength()) + throw new Exception("Maximum length reached"); + + for (int index = 0; index < bitBuffer.GetBitLength(); index++, BitLength++) + Data.Set(BitLength, bitBuffer.Data.Get(index)); + } + + /// + /// Returns a new copy of this buffer. + /// + /// A new copy of this buffer (not null) + public BitBuffer Clone() + { + var result = new BitBuffer().Clone(); + result.Data = (BitSet)result.Data.Clone(); + return result; + } + } +} diff --git a/csharp/QrCodeGenerator/BitSet/BitSet.cs b/csharp/QrCodeGenerator/BitSet/BitSet.cs new file mode 100644 index 0000000..2214d5b --- /dev/null +++ b/csharp/QrCodeGenerator/BitSet/BitSet.cs @@ -0,0 +1,701 @@ +/* BitSet.cs -- A vector of bits. + Copyright (C) 1998, 1999, 2000, 2001, 2004, 2005 Free Software Foundation, Inc. + + This file is part of GNU Classpath. + + GNU Classpath is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + GNU Classpath is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Classpath; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. + + Linking this library statically or dynamically with other modules is + making a combined work based on this library. Thus, the terms and + conditions of the GNU General Public License cover the whole + combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent + modules, and to copy and distribute the resulting executable under + terms of your choice, provided that you also meet, for each linked + independent module, the terms and conditions of the license of that + module. An independent module is a module which is not derived from + or based on this library. If you modify this library, you may extend + this exception to your version of the library, but you are not + obligated to do so. If you do not wish to do so, delete this + exception statement from your version. */ + +/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3 + * hashCode algorithm taken from JDK 1.2 docs. + */ + +// Source ported to C# from: http://fuseyism.com/classpath/doc/java/util/BitSet-source.html + +namespace Util +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// This class can be thought of in two ways. You can see it as a + /// vector of bits or as a set of non-negative integers. The name + /// BitSet is a bit misleading. + /// + /// It is implemented by a bit vector, but its equally possible to see + /// it as set of non-negative integer; each integer in the set is + /// represented by a set bit at the corresponding index. The size of + /// this structure is determined by the highest integer in the set. + /// + /// You can union, intersect and build (symmetric) remainders, by + /// invoking the logical operations and, or, andNot, resp. xor. + /// + /// This implementation is NOT synchronized against concurrent access from + /// multiple threads. Specifically, if one thread is reading from a bitset + /// while another thread is simultaneously modifying it, the results are + /// undefined. + /// + /// author Jochen Hoenicke + /// author Tom Tromey (tromey@cygnus.com) + /// author Eric Blake (ebb9@email.byu.edu) + /// status updated to 1.4 + /// + [Serializable] + public class BitSet : ICloneable + { + private const long serialVersionUID = 7997698588986878753L; + + /// + /// A common mask. + /// + private const int LONG_MASK = 0x3f; + + /// + /// The actual bits. + /// @serial the i'th bit is in bits[i/64] at position i%64 (where position + /// 0 is the least significant). + /// + private long[] bits; + + /// + /// Create a new empty bit set. All bits are initially false. + /// + public BitSet() + : this(64) + { + } + + /// + /// Create a new empty bit set, with a given size. This + /// constructor reserves enough space to represent the integers + /// from 0 to nbits-1. + /// + /// nbits the initial size of the bit set + public BitSet(int nbits) + { + if (nbits < 0) throw new ArgumentOutOfRangeException("nbits may not be negative"); + + var length = (uint)nbits >> 6; + if ((nbits & LONG_MASK) != 0) + length++; + bits = new long[length]; + } + + /// + /// Performs the logical AND operation on this bit set and the + /// given set. This means it builds the intersection + /// of the two sets. The result is stored into this bit set. + /// + /// the second bit set + public void And(BitSet bs) + { + var max = Math.Min(bits.Length, bs.bits.Length); + int i; + for (i = 0; i < max; ++i) + bits[i] &= bs.bits[i]; + while (i < bits.Length) + bits[i++] = 0; + } + + /// + /// Performs the logical AND operation on this bit set and the + /// complement of the given bs. This means it + /// selects every element in the first set, that isn't in the + /// second set. The result is stored into this bit set and is + /// effectively the set difference of the two. + /// + /// the second bit set + public void AndNot(BitSet bs) + { + var i = Math.Min(bits.Length, bs.bits.Length); + while (--i >= 0) + bits[i] &= ~bs.bits[i]; + } + + /// + /// Returns the number of bits set to true. + /// + public int Cardinality() + { + uint card = 0; + for (int i = bits.Length - 1; i >= 0; i--) + { + var a = bits[i]; + // Take care of common cases. + if (a == 0) + continue; + if (a == -1) + { + card += 64; + continue; + } + + // Successively collapse alternating bit groups into a sum. + a = ((a >> 1) & 0x5555555555555555L) + (a & 0x5555555555555555L); + a = ((a >> 2) & 0x3333333333333333L) + (a & 0x3333333333333333L); + var b = (uint)((a >> 32) + a); + b = ((b >> 4) & 0x0f0f0f0f) + (b & 0x0f0f0f0f); + b = ((b >> 8) & 0x00ff00ff) + (b & 0x00ff00ff); + card += ((b >> 16) & 0x0000ffff) + (b & 0x0000ffff); + } + return (int)card; + } + + /// + /// Sets all bits in the set to false. + /// + //public void Clear() + //{ + // Arrays.Fill(bits, 0); + //} + + /// + /// Removes the integer pos from this set. That is + /// the corresponding bit is cleared. If the index is not in the set, + /// this method does nothing. + /// + /// a non-negative integer + public void Clear(int pos) + { + var offset = pos >> 6; + Ensure(offset); + bits[offset] &= ~(1L << pos); + } + + /// + /// Sets the bits between from (inclusive) and to (exclusive) to false. + /// + /// the start range (inclusive) + /// the end range (exclusive) + public void Clear(int from, int to) + { + if (from < 0 || from > to) + throw new ArgumentOutOfRangeException(); + if (from == to) + return; + var lo_offset = (uint)from >> 6; + var hi_offset = (uint)to >> 6; + Ensure((int)hi_offset); + if (lo_offset == hi_offset) + { + bits[hi_offset] &= ((1L << from) - 1) | (-1L << to); + return; + } + + bits[lo_offset] &= (1L << from) - 1; + bits[hi_offset] &= -1L << to; + for (int i = (int)lo_offset + 1; i < hi_offset; i++) + bits[i] = 0; + } + + /// + /// Create a clone of this bit set, that is an instance of the same + /// class and contains the same elements. But it doesn't change when + /// this bit set changes. + /// + /// the clone of this object. + public object Clone() + { + try + { + var bs = ObjectCopier.Clone(this); + bs.bits = (long[])bits.Clone(); + return bs; + } + catch + { + // Impossible to get here. + return null; + } + } + + /// + /// Returns true if the obj is a bit set that contains + /// exactly the same elements as this bit set, otherwise false. + /// + /// the object to compare to + /// true if obj equals this bit set + public override bool Equals(object obj) + { + if (!(obj.GetType() == typeof(BitSet))) + return false; + + var bs = (BitSet)obj; + var max = Math.Min(bits.Length, bs.bits.Length); + int i; + for (i = 0; i < max; ++i) + if (bits[i] != bs.bits[i]) + return false; + + // If one is larger, check to make sure all extra bits are 0. + for (int j = i; j < bits.Length; ++j) + if (bits[j] != 0) + return false; + for (int j = i; j < bs.bits.Length; ++j) + if (bs.bits[j] != 0) + return false; + return true; + } + + /// + /// Sets the bit at the index to the opposite value. + /// + /// the index of the bit + public void Flip(int index) + { + var offset = index >> 6; + Ensure(offset); + bits[offset] ^= 1L << index; + } + + /// + /// Sets a range of bits to the opposite value. + /// + /// the low index (inclusive) + /// the high index (exclusive) + public void Flip(int from, int to) + { + if (from < 0 || from > to) + throw new ArgumentOutOfRangeException(); + if (from == to) + return; + var lo_offset = (uint)from >> 6; + var hi_offset = (uint)to >> 6; + Ensure((int)hi_offset); + + if (lo_offset == hi_offset) + { + bits[hi_offset] ^= (-1L << from) & ((1L << to) - 1); + return; + } + + bits[lo_offset] ^= -1L << from; + bits[hi_offset] ^= (1L << to) - 1; + for (int i = (int)lo_offset + 1; i < hi_offset; i++) + bits[i] ^= -1; + } + + /// + /// Returns true if the integer bitIndex is in this bit + /// set, otherwise false. + /// + /// a non-negative integer + /// the value of the bit at the specified position + public Boolean Get(int pos) + { + var offset = pos >> 6; + if (offset >= bits.Length) + return false; + return (bits[offset] & (1L << pos)) != 0; + } + + /// + /// Returns a new BitSet composed of a range of bits from + /// this one. + /// + /// the low index (inclusive) + /// the high index (exclusive) + /// + public BitSet Get(int from, int to) + { + if (from < 0 || from > to) + throw new ArgumentOutOfRangeException(); + var bs = new BitSet(to - from); + var lo_offset = (uint)from >> 6; + if (lo_offset >= bits.Length || to == from) + return bs; + + var lo_bit = from & LONG_MASK; + var hi_offset = (uint)to >> 6; + if (lo_bit == 0) + { + uint len = Math.Min(hi_offset - lo_offset + 1, (uint)bits.Length - lo_offset); + Array.Copy(bits, lo_offset, bs.bits, 0, len); + if (hi_offset < bits.Length) + bs.bits[hi_offset - lo_offset] &= (1L << to) - 1; + return bs; + } + + var len2 = Math.Min(hi_offset, (uint)bits.Length - 1); + var reverse = 64 - lo_bit; + int i; + for (i = 0; lo_offset < len2; lo_offset++, i++) + bs.bits[i] = ((bits[lo_offset] >> lo_bit) | (bits[lo_offset + 1] << reverse)); + if ((to & LONG_MASK) > lo_bit) + bs.bits[i++] = bits[lo_offset] >> lo_bit; + if (hi_offset < bits.Length) + bs.bits[i - 1] &= (1L << (to - from)) - 1; + return bs; + } + + /// + /// Returns a hash code value for this bit set. The hash code of + /// two bit sets containing the same integers is identical. The algorithm + /// used to compute it is as follows: + /// + /// Suppose the bits in the BitSet were to be stored in an array of + /// long integers called bits, in such a manner that + /// bit k is set in the BitSet (for non-negative values + /// of k) if and only if + /// + /// ((k/64) < bits.length) + /// && ((bits[k/64] & (1L << (bit % 64))) != 0) + /// + /// + /// Then the following definition of the GetHashCode method + /// would be a correct implementation of the actual algorithm: + /// + ///
public override int GetHashCode()
+        /// {
+        ///   long h = 1234;
+        ///   for (int i = bits.length-1; i >= 0; i--)
+        ///   {
+        ///     h ^= bits[i] * (i + 1);
+        ///   }
+        ///   
+        ///   return (int)((h >> 32) ^ h);
+        /// }
+ /// + /// Note that the hash code values changes, if the set is changed. + ///
+ /// the hash code value for this bit set. + public override int GetHashCode() + { + long h = 1234; + for (int i = bits.Length; i > 0;) + h ^= i * bits[--i]; + return (int)((h >> 32) ^ h); + } + + /// + /// Returns true if the specified BitSet and this one share at least one + /// common true bit. + /// + /// the set to check for intersection + /// true if the sets intersect + public bool Intersects(BitSet set) + { + var i = Math.Min(bits.Length, set.bits.Length); + while (--i >= 0) + { + if ((bits[i] & set.bits[i]) != 0) + return true; + } + return false; + } + + /// + /// Returns true if this set contains no true bits. + /// + /// true if all bits are false + public bool IsEmpty() + { + for (int i = bits.Length - 1; i >= 0; i--) + { + if (bits[i] != 0) + return false; + } + return true; + } + + /// + /// Gets the logical number of bits actually used by this bit + /// set. It returns the index of the highest set bit plus one. + /// Note that this method doesn't return the number of set bits. + /// + /// Returns the index of the highest set bit plus one. + /// + public int Length + { + get + { + // Set i to highest index that contains a non-zero value. + int i; + for (i = bits.Length - 1; i >= 0 && bits[i] == 0; --i) { } + + // if i < 0 all bits are cleared. + if (i < 0) + return 0; + + // Now determine the exact length. + var b = bits[i]; + var len = (i + 1) * 64; + // b >= 0 checks if the highest bit is zero. + while (b >= 0) + { + --len; + b <<= 1; + } + + return len; + } + } + + /// + /// Returns the index of the next false bit, from the specified bit + /// (inclusive). + /// + /// the start location + /// the first false bit + public int NextClearBit(int from) + { + var offset = from >> 6; + var mask = 1L << from; + while (offset < bits.Length) + { + var h = bits[offset]; + do + { + if ((h & mask) == 0) + return from; + mask <<= 1; + from++; + } + while (mask != 0); + + mask = 1; + offset++; + } + + return from; + } + + /// + /// Returns the index of the next true bit, from the specified bit + /// (inclusive). If there is none, -1 is returned. You can iterate over + /// all true bits with this loop:
+ /// + ///
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
+        /// {
+        ///   // operate on i here
+        /// }
+        /// 
+ ///
+ /// the start location + /// the first true bit, or -1 + public int NextSetBit(int from) + { + var offset = from >> 6; + var mask = 1L << from; + while (offset < bits.Length) + { + var h = bits[offset]; + do + { + if ((h & mask) != 0) + return from; + mask <<= 1; + from++; + } + while (mask != 0); + + mask = 1; + offset++; + } + + return -1; + } + + /// + /// Performs the logical OR operation on this bit set and the + /// given set. This means it builds the union + /// of the two sets. The result is stored into this bit set, which + /// grows as necessary. + /// + /// the second bit set + public void Or(BitSet bs) + { + Ensure(bs.bits.Length - 1); + for (int i = bs.bits.Length - 1; i >= 0; i--) + bits[i] |= bs.bits[i]; + } + + /// + /// Add the integer bitIndex to this set. That is + /// the corresponding bit is set to true. If the index was already in + /// the set, this method does nothing. The size of this structure + /// is automatically increased as necessary. + /// + /// a non-negative integer. + public void Set(int pos) + { + var offset = pos >> 6; + Ensure(offset); + bits[offset] |= 1L << pos; + } + + /// + /// Sets the bit at the given index to the specified value. The size of + /// this structure is automatically increased as necessary. + /// + /// the position to set + /// the value to set it to + public void Set(int index, bool value) + { + if (value) + Set(index); + else + Clear(index); + } + + /// + /// Sets the bits between from (inclusive) and to (exclusive) to true. + /// + /// the start range (inclusive) + /// the end range (exclusive) + public void Set(int from, int to) + { + if (from < 0 || from > to) + throw new ArgumentOutOfRangeException(); + if (from == to) + return; + var lo_offset = (uint)from >> 6; + var hi_offset = (uint)to >> 6; + Ensure((int)hi_offset); + if (lo_offset == hi_offset) + { + bits[hi_offset] |= (-1L << from) & ((1L << to) - 1); + return; + } + + bits[lo_offset] |= -1L << from; + bits[hi_offset] |= (1L << to) - 1; + for (int i = (int)lo_offset + 1; i < hi_offset; i++) + bits[i] = -1; + } + + /// + /// Sets the bits between from (inclusive) and to (exclusive) to the + /// specified value. + /// + /// the start range (inclusive) + /// the end range (exclusive) + /// the value to set it to + public void Set(int from, int to, bool value) + { + if (value) + Set(from, to); + else + Clear(from, to); + } + + /// + /// Returns the number of bits actually used by this bit set. Note + /// that this method doesn't return the number of set bits, and that + /// future requests for larger bits will make this automatically grow. + /// + /// Returns the number of bits currently used. + /// + public int Size + { + get + { + return bits.Length * 64; + } + } + + /// + /// Returns the string representation of this bit set. This + /// consists of a comma separated list of the integers in this set + /// surrounded by curly braces. There is a space after each comma. + /// A sample string is thus "{1, 3, 53}". + /// + /// the string representation. + public override string ToString() + { + var r = new StringBuilder("{"); + var first = true; + for (int i = 0; i < bits.Length; ++i) + { + var bit = 1; + var word = bits[i]; + if (word == 0) + continue; + for (int j = 0; j < 64; ++j) + { + if ((word & bit) != 0) + { + if (!first) + r.Append(", "); + r.Append(64 * i + j); + first = false; + } + bit <<= 1; + } + } + + return r.Append("}").ToString(); + } + + /// + /// Performs the logical XOR operation on this bit set and the + /// given set. This means it builds the symmetric + /// remainder of the two sets (the elements that are in one set, + /// but not in the other). The result is stored into this bit set, + /// which grows as necessary. + /// + /// the second bit set + public void XOr(BitSet bs) + { + Ensure(bs.bits.Length - 1); + for (int i = bs.bits.Length - 1; i >= 0; i--) + bits[i] ^= bs.bits[i]; + } + + /// + /// Make sure the vector is big enough. + /// + /// the size needed for the bits array + private void Ensure(int lastElt) + { + if (lastElt >= bits.Length) + { + var nd = new long[lastElt + 1]; + Array.Copy(bits, 0, nd, 0, bits.Length); + bits = nd; + } + } + + // This is used by EnumSet for efficiency. + public bool ContainsAll(BitSet other) + { + for (int i = other.bits.Length - 1; i >= 0; i--) + { + if ((bits[i] & other.bits[i]) != other.bits[i]) + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/csharp/QrCodeGenerator/BitSet/ObjectCopier.cs b/csharp/QrCodeGenerator/BitSet/ObjectCopier.cs new file mode 100644 index 0000000..65d476b --- /dev/null +++ b/csharp/QrCodeGenerator/BitSet/ObjectCopier.cs @@ -0,0 +1,44 @@ +namespace Util +{ + using System; + using System.IO; + using System.Runtime.Serialization; + using System.Runtime.Serialization.Formatters.Binary; + + /// + /// Reference Article http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx + /// Provides a method for performing a deep copy of an object. + /// Binary Serialization is used to perform the copy. + /// + public static class ObjectCopier + { + /// + /// Perform a deep Copy of the object. + /// + /// The type of object being copied. + /// The object instance to copy. + /// The copied object. + public static T Clone(T source) + { + if (!typeof(T).IsSerializable) + { + throw new ArgumentException("The type must be serializable.", "source"); + } + + // Don't serialize a null object, simply return the default for that object + if (Object.ReferenceEquals(source, null)) + { + return default(T); + } + + IFormatter formatter = new BinaryFormatter(); + Stream stream = new MemoryStream(); + using (stream) + { + formatter.Serialize(stream, source); + stream.Seek(0, SeekOrigin.Begin); + return (T)formatter.Deserialize(stream); + } + } + } +} \ No newline at end of file diff --git a/csharp/QrCodeGenerator/CustomExceptions/DataTooLongException.cs b/csharp/QrCodeGenerator/CustomExceptions/DataTooLongException.cs new file mode 100644 index 0000000..41aff0c --- /dev/null +++ b/csharp/QrCodeGenerator/CustomExceptions/DataTooLongException.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace QrCodeGenerator +{ + /// + /// Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: + /// + /// Decrease the error correction level if it was greater than ErrorCorrection.Low. + /// If the advanced EncodeSegments() function with 6 arguments or the MakeSegmentsOptimally() + /// function was called, then increase the maxVersion argument + /// if it was less than . (This advice does not apply to the other + /// factory functions because they search all versions up to .) + /// Change the text or binary data to be shorter. + /// Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). + /// Propagate the error upward to the caller/user. + /// + /// See also: + /// + /// + /// + /// + /// + /// + /// + public class DataTooLongException : Exception + { + public DataTooLongException() + { + } + + public DataTooLongException(string message): base(message) + { + } + + } +} diff --git a/csharp/QrCodeGenerator/ErrorCorrection.cs b/csharp/QrCodeGenerator/ErrorCorrection.cs new file mode 100644 index 0000000..9950bfe --- /dev/null +++ b/csharp/QrCodeGenerator/ErrorCorrection.cs @@ -0,0 +1,36 @@ +using System; + +namespace QrCodeGenerator +{ + /// + /// The error correction level in a QR Code symbol. + /// + public class ErrorCorrection + { + public enum Level + { + Low, //The QR Code can tolerate about 7% erroneous codewords. + Medium, //The QR Code can tolerate about 15% erroneous codewords. + Quartile, //The QR Code can tolerate about 25% erroneous codewords. + High //The QR Code can tolerate about 30% erroneous codewords. + } + + public int FormatBits; // In the range 0 to 3 (unsigned 2-bit integer). + public Level LevelCode; + + public ErrorCorrection(Level level) + { + FormatBits = GetFormatBits(level); + LevelCode = level; + } + + public int GetFormatBits(Level level) + { + if ((int)level == 0) return 1; + else if ((int)level == 1) return 0; + else if ((int)level == 2) return 3; + else if ((int)level == 3) return 2; + else throw new ArgumentException(); + } + } +} diff --git a/csharp/QrCodeGenerator/Mode.cs b/csharp/QrCodeGenerator/Mode.cs new file mode 100644 index 0000000..45942b2 --- /dev/null +++ b/csharp/QrCodeGenerator/Mode.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace QrCodeGenerator +{ + /// + /// Describes how a segment's data bits are interpreted. + /// + public class Mode + { + /// + /// The mode indicator bits, which is a uint4 value (range 0 to 15). + /// + public int ModeBits { get; set; } + + /// + /// Number of character count bits for three different version ranges. + /// + public int[] NumBitsCharCount { get; set; } + + public static Mode Numeric = new Mode(0x1, new int[] { 10, 12, 14 }); + public static Mode AlphaNumeric = new Mode(0x2, new int[] { 9, 11, 13 }); + public static Mode Byte = new Mode(0x4, new int[] { 8, 16, 16 }); + public static Mode Kanji = new Mode(0x8, new int[] { 8, 10, 12 }); + public static Mode Eci = new Mode(0x7, new int[] { 0, 0, 0 }); + + private Mode(int mode, int[] ccbits) + { + ModeBits = mode; + NumBitsCharCount = ccbits; + } + + /// + /// Returns the bit width of the character count field for a segment in this mode + /// in a QR Code at the given version number. The result is in the range [0, 16]. + /// + /// + /// + public int NumCharCountBits(int ver) + { + return NumBitsCharCount[(int)Math.Floor((decimal)(ver + 7) / 17)]; + } + + } +} diff --git a/csharp/QrCodeGenerator/QrCode.cs b/csharp/QrCodeGenerator/QrCode.cs new file mode 100644 index 0000000..b3706ed --- /dev/null +++ b/csharp/QrCodeGenerator/QrCode.cs @@ -0,0 +1,822 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; + +namespace QrCodeGenerator +{ + public class QrCode + { + /// + /// The version number of this QR Code, which is between 1 and 40 (inclusive). This determines the size of this barcode. + /// + public int Version { get; set; } + + /// + /// The width and height of this QR Code, measured in modules, between 21 and 177 (inclusive). This is equal to version × 4 + 17. + /// + public int Size { get; set; } + + /// + /// The error correction level used in this QR Code, which is not null. + /// + public ErrorCorrection ErrorCorrectionLevel { get; set; } + + /// + /// The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + /// Even if a QR Code is created with automatic masking requested (mask = + /// −1), the resulting object still has a mask value between 0 and 7. + /// + public int Mask { get; set; } + + /// + /// The modules of this QR Code (false = white, true = black). + /// Immutable after constructor finishes. Accessed through GetModule(). + /// + public bool[,] Modules { get; set; } + + /// + /// Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + /// + public bool[,] IsFunction { get; set; } + + public static int MinVersion = 1; //The minimum version number (1) supported in the QR Code Model 2 standard. + public static int MaxVersion = 40; //The maximum version number (40) supported in the QR Code Model 2 standard. + + //For use in GetPenaltyScore(), when evaluating which mask is best. + private static int PenaltyN1 = 3; + private static int PenaltyN2 = 3; + private static int PenaltyN3 = 40; + private static int PenaltyN4 = 10; + + private static sbyte[][] ErrorCorrectionCodeWordsPerBlock = new sbyte[][] { + // Version: (note that index 0 is for padding, and is set to an illegal value) + new sbyte[]{-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + new sbyte[]{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + new sbyte[]{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + new sbyte[]{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High + }; + + private static sbyte[][] NumErrorCorrectionBlocks = new sbyte[][] { + // Version: (note that index 0 is for padding, and is set to an illegal value) + new sbyte[]{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + new sbyte[]{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + new sbyte[]{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + new sbyte[]{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High + }; + + /// + /// Returns a QR Code representing the specified Unicode text string at the specified error correction level. + /// As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer + /// Unicode code points(not UTF-16 code units) if the low error correction level is used.The smallest possible + /// QR Code version is automatically chosen for the output.The ErrorCorrection level of the result may be higher than the + /// ecl argument if it can be done without increasing the version. + /// + /// The version number to use, which must be in the range 1 to 40 (inclusive) + /// The error correction level to use + /// The bytes representing segments to encode (without ErrorCorrection) + /// The mask pattern to use, which is either −1 for automatic choice or from 0 to 7 for fixed choice + /// If the byte array is null + /// If the version or mask value is out of range, or if the data is the wrong length for the specified version and error correction level + public QrCode(int version, ErrorCorrection.Level errorCorrectionLevel, sbyte[] dataCodeWords, int mask) + { + if (version < MinVersion || version > MaxVersion) + throw new ArgumentException("Version value out of range"); + if (mask < -1 || mask > 7) + throw new ArgumentException("Mask value out of range"); + if (dataCodeWords == null) + throw new ArgumentNullException("Requiered non null dataCodeWords array"); + + Version = version; + Size = version * 4 + 17; + + ErrorCorrectionLevel = new ErrorCorrection(errorCorrectionLevel); + Modules = new bool[Size, Size]; + IsFunction = new bool[Size, Size]; + + DrawFunctionPatterns(); + + var allCodewords = AddErrorCorrectionAndInterleave(dataCodeWords); + DrawCodewords(allCodewords); + Mask = HandleConstructorMasking(mask); + IsFunction = null; + } + + /// + /// Returns a QR Code representing the specified Unicode text string at the specified error correction level. + /// As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer + /// Unicode code points (not UTF-16 code units) if the low error correction level is used. The smallest possible + /// QR Code version is automatically chosen for the output. The ErrorCorrection level of the result may be higher than the + /// ecl argument if it can be done without increasing the version. + /// + /// The text to be encoded (not null), which can be any Unicode string + /// + /// A QR Code (not null) representing the text + /// If the text is null + /// If the text fails to fit in the largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeText(string text, ErrorCorrection errorCorrectionLevel) + { + if (text == null) + throw new ArgumentNullException("Text is null!"); + + var segments = QrSegment.MakeSegments(text); + return EncodeSegments(segments, errorCorrectionLevel); + } + + /// + /// Returns a QR Code representing the specified binary data at the specified error correction level. + /// This function always encodes using the binary segment mode, not any text mode. The maximum number of + /// bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + /// The ErrorCorrection level of the result may be higher than the ecl argument if it can be done without increasing the version. + /// + /// The binary data to encode (not null) + /// The error correction level to use (not null) (boostable) + /// A QR Code (not null) representing the data + /// If the data is null + /// If the data fails to fit in the largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeBinary(sbyte[] data, ErrorCorrection errorCorrectionLevel) + { + if (data == null) + throw new ArgumentNullException("Requiered non null data array"); + + return EncodeSegments(new List { QrSegment.MakeBytes(data) }, errorCorrectionLevel); + } + + /// + /// Returns a QR Code representing the specified segments at the specified error correction + /// level. The smallest possible QR Code version is automatically chosen for the output. The ErrorCorrection level + /// of the result may be higher than the ecl argument if it can be done without increasing the version. + /// This function allows the user to create a custom sequence of segments that switches + /// between modes (such as alphanumeric and byte) to encode text in less space. + /// This is a mid-level API; the high-level API is + /// and . + /// + /// The segments to encode + /// The error correction level to use (not null) (boostable) + /// A QR Code (not null) representing the segments + /// If the list of segments or any segment is null + /// If the segments fails to fit in the largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeSegments(List segments, ErrorCorrection errorCorrectionLevel) + { + if (segments == null) + throw new ArgumentNullException("Requiered non null list"); + + return EncodeSegments(segments, errorCorrectionLevel, MinVersion, MaxVersion, -1, true); + } + + /// + /// Returns a QR Code representing the specified segments with the specified encoding parameters. + /// The smallest possible QR Code version within the specified range is automatically + /// chosen for the output.Iff boostEcl is true, then the ErrorCorrection level of the + /// result may be higher than the ecl argument if it can be done without increasing + /// the version.The mask number is either between 0 to 7 (inclusive) to force that + /// mask, or −1 to automatically choose an appropriate mask (which may be slow). + /// This function allows the user to create a custom sequence of segments that switches + /// between modes (such as alphanumeric and byte) to encode text in less space. + /// This is a mid-level API; the high-level API is + /// and . + /// + /// The segments to encode + /// The error correction level to use (not null) (boostable) + /// The minimum allowed version of the QR Code (at least 1) + /// The maximum allowed version of the QR Code (at most 40) + /// The mask number to use (between 0 and 7 (inclusive)), or −1 for automatic mask + /// Increases the ErrorCorrection level as long as it doesn't increase the version number + /// A QR Code (not null) representing the segments + /// If the list of segments, any segment, or error correction level is null + /// If 1 ≤ minVersion ≤ maxVersion ≤ 40 or −1 ≤ mask ≤ 7 is violated + /// If the segments fails to fit in the largest version QR Code at the ECL, which means it is too long + public static QrCode EncodeSegments(List segments, ErrorCorrection errorCorrectionLevel, int minVersion, int maxVersion, int mask, bool boostErrorCorrectionLevel) + { + if (segments == null) + throw new ArgumentNullException("Requiered non null list"); + if (!(MinVersion <= minVersion && minVersion <= maxVersion && maxVersion <= MaxVersion) || mask < -1 || mask > 7) + throw new ArgumentException("Invalid value"); + + int version, dataUsedBits, dataCapacityBits; + for (version = minVersion; ; version++) + { + dataCapacityBits = GetNumDataCodewords(version, errorCorrectionLevel.LevelCode) * 8; + dataUsedBits = QrSegment.GetTotalBits(segments, version); + + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) break; + + if (version >= maxVersion) + { + var message = "Segment too long"; + if (dataUsedBits != -1) + message = string.Format("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits); + throw new DataTooLongException(message); + } + } + + foreach (var newEcl in (ErrorCorrection.Level[])Enum.GetValues(typeof(ErrorCorrection.Level))) + { + if (boostErrorCorrectionLevel && dataUsedBits <= GetNumDataCodewords(version, newEcl) * 8) + errorCorrectionLevel.LevelCode = newEcl; + } + + var bitBuffer = new BitBuffer(); + foreach (var segment in segments) + { + bitBuffer.AppendBits(segment.Mode.ModeBits, 4); + bitBuffer.AppendBits(segment.CharactersCount, segment.Mode.NumCharCountBits(version)); + bitBuffer.AppendData(segment.Data); + } + + dataCapacityBits = GetNumDataCodewords(version, errorCorrectionLevel.LevelCode) * 8; + + bitBuffer.AppendBits(0, Math.Min(4, dataCapacityBits - bitBuffer.GetBitLength())); + bitBuffer.AppendBits(0, (8 - bitBuffer.GetBitLength() % 8) % 8); + + for (int padByte = 0xEC; bitBuffer.GetBitLength() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bitBuffer.AppendBits(padByte, 8); + + var dataCodewords = new sbyte[bitBuffer.GetBitLength() / 8]; + for (int i = 0; i < bitBuffer.GetBitLength(); i++) + dataCodewords[i >> 3] |= (sbyte)(bitBuffer.GetBit(i) << (7 - (i & 7))); + + return new QrCode(version, errorCorrectionLevel.LevelCode, dataCodewords, mask); + } + + /// + /// Returns the color of the module (pixel) at the specified coordinates, which is false + /// for white or true for black. The top left corner has the coordinates (x=0, y=0). + /// If the specified coordinates are out of bounds, then false (white) is returned. + /// + /// The x coordinate, where 0 is the left edge and size−1 is the right edge + /// The y coordinate, where 0 is the top edge and size−1 is the bottom edge + /// True if the coordinates are in bounds and the module at that location is black, or False (white) otherwise + public bool GetModule(int x, int y) + { + return 0 <= x && x < Size && 0 <= y && y < Size && Modules[y, x]; + } + + /// + /// Returns a raster image depicting this QR Code, with the specified module scale and border modules. + /// For example, ToImage(scale=10, border=4) means to pad the QR Code with 4 white + /// border modules on all four sides, and use 10×10 pixels to represent each module. + /// The resulting image only contains the hex colors 000000 and FFFFFF. + /// + /// The side length (measured in pixels, must be positive) of each module + /// The number of border modules to add, which must be non-negative + /// A new image representing this QR Code, with padding and scaling + /// If the scale or border is out of range, or if {scale, border, size} cause the image dimensions to exceed Integer.MAX_VALUE + public Bitmap ToImage(int scale, int border) + { + if (scale <= 0 || border < 0) + throw new ArgumentException("Value out of range"); + if (border > int.MaxValue / 2 || Size + border * 2L > int.MaxValue / scale) + throw new ArgumentException("Scale or border too large"); + + var result = new Bitmap((Size + border * 2) * scale, (Size + border * 2) * scale, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + for (int y = 0; y < result.Height; y++) + { + for (int x = 0; x < result.Width; x++) + { + var color = GetModule(x / scale - border, y / scale - border); + result.SetPixel(x, y, color ? Color.Black : Color.White); + } + } + return result; + } + + /// + /// Returns a string of SVG code for an image depicting this QR Code, with the specified number + /// of border modules. The string always uses Unix newlines (\n), regardless of the platform. + /// + /// The number of border modules to add, which must be non-negative + /// A string representing this QR Code as an SVG XML document + /// If the border is negative + public string ToSvgString(int border) + { + if (border < 0) + throw new ArgumentException("Border must be non-negative"); + + var stringBuilder = new StringBuilder() + .Append("\n") + .Append("\n") + .Append(string.Format("\n", Size + border * 2)) + .Append("\t\n") + .Append("\t\n").Append("\n").ToString(); + } + + #region Private helper methods for constructor - Drawing function modules + + // Reads this object's version field, and draws and marks all function modules. + private void DrawFunctionPatterns() + { + for (int i = 0; i < Size; i++) + { + SetFunctionModule(6, i, i % 2 == 0); + SetFunctionModule(i, 6, i % 2 == 0); + } + + DrawFinderPattern(3, 3); + DrawFinderPattern(Size - 4, 3); + DrawFinderPattern(3, Size - 4); + + var alignPatPos = GetAlignmentPatternPositions(); + var numAlign = alignPatPos.Length; + for (int i = 0; i < numAlign; i++) + { + for (int j = 0; j < numAlign; j++) + { + if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) + DrawAlignmentPattern(alignPatPos[i], alignPatPos[j]); + } + } + + DrawFormatBits(0); + DrawVersion(); + } + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + private void DrawFormatBits(int msk) + { + var data = ErrorCorrectionLevel.FormatBits << 3 | msk; + var rem = data; + + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + + var bits = (data << 10 | rem) ^ 0x5412; + + for (int i = 0; i <= 5; i++) + SetFunctionModule(8, i, GetBit(bits, i)); + + SetFunctionModule(8, 7, GetBit(bits, 6)); + SetFunctionModule(8, 8, GetBit(bits, 7)); + SetFunctionModule(7, 8, GetBit(bits, 8)); + + for (int i = 9; i < 15; i++) + SetFunctionModule(14 - i, 8, GetBit(bits, i)); + + for (int i = 0; i < 8; i++) + SetFunctionModule(Size - 1 - i, 8, GetBit(bits, i)); + + for (int i = 8; i < 15; i++) + SetFunctionModule(8, Size - 15 + i, GetBit(bits, i)); + + SetFunctionModule(8, Size - 8, true); + } + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field, iff 7 <= version <= 40. + private void DrawVersion() + { + if (Version < 7) + return; + + var rem = Version; + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + + var bits = Version << 12 | rem; + for (int i = 0; i < 18; i++) + { + var bit = GetBit(bits, i); + var a = Size - 11 + i % 3; + var b = i / 3; + SetFunctionModule(a, b, bit); + SetFunctionModule(b, a, bit); + } + } + + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. + private void DrawFinderPattern(int x, int y) + { + for (int dy = -4; dy <= 4; dy++) + { + for (int dx = -4; dx <= 4; dx++) + { + int dist = Math.Max(Math.Abs(dx), Math.Abs(dy)); + int xx = x + dx, yy = y + dy; + if (0 <= xx && xx < Size && 0 <= yy && yy < Size) + SetFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } + } + + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. + private void DrawAlignmentPattern(int x, int y) + { + for (int dy = -2; dy <= 2; dy++) + { + for (int dx = -2; dx <= 2; dx++) + SetFunctionModule(x + dx, y + dy, Math.Max(Math.Abs(dx), Math.Abs(dy)) != 1); + } + } + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in bounds. + private void SetFunctionModule(int x, int y, bool isBlack) + { + Modules[y, x] = isBlack; + IsFunction[y, x] = true; + } + + #endregion + + #region Private helper methods for constructor - Codewords and masking + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + private sbyte[] AddErrorCorrectionAndInterleave(sbyte[] data) + { + if (data.Length != GetNumDataCodewords(Version, ErrorCorrectionLevel.LevelCode)) + throw new ArgumentException(); + + var numBlocks = NumErrorCorrectionBlocks[(int)ErrorCorrectionLevel.LevelCode][Version]; + var blockErrorCorrectionLen = ErrorCorrectionCodeWordsPerBlock[(int)ErrorCorrectionLevel.LevelCode][Version]; + var rawCodewords = GetNumRawDataModules(Version) / 8; + var numShortBlocks = numBlocks - rawCodewords % numBlocks; + var shortBlockLen = rawCodewords / numBlocks; + + var blocks = new sbyte[numBlocks][]; + var rsDiv = ReedSolomonComputeDivisor(blockErrorCorrectionLen); + for (int i = 0, k = 0; i < numBlocks; i++) + { + var dat = ArrayCopyOfRange(data, k, k + shortBlockLen - blockErrorCorrectionLen + (i < numShortBlocks ? 0 : 1)); + k += dat.Length; + + var block = new sbyte[shortBlockLen + 1]; + Array.Copy(dat, block, dat.Length) ; + + var ErrorCorrection = ReedSolomonComputeRemainder(dat, rsDiv); + Array.Copy(ErrorCorrection, 0, block, block.Length - blockErrorCorrectionLen, ErrorCorrection.Length); + blocks[i] = block; + } + + var result = new sbyte[rawCodewords]; + for (int i = 0, k = 0; i < blocks[0].Length; i++) + { + for (int j = 0; j < blocks.Length; j++) + { + if (i != shortBlockLen - blockErrorCorrectionLen || j >= numShortBlocks) + { + result[k] = blocks[j][i]; + k++; + } + } + } + return result; + } + + sbyte[] ArrayCopyOfRange(sbyte[] src, int start, int end) + { + var len = end - start; + var dest = new sbyte[len]; + + for (int i = 0; i < len; i++) + { + dest[i] = src[start + i]; + } + return dest; + } + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code. Function modules need to be marked off before this is called. + private void DrawCodewords(sbyte[] data) + { + if (data.Length != GetNumRawDataModules(Version) / 8) + throw new ArgumentException(); + + var i = 0; + + for (int right = Size - 1; right >= 1; right -= 2) + { + if (right == 6) + right = 5; + for (int vert = 0; vert < Size; vert++) + { + for (int j = 0; j < 2; j++) + { + var x = right - j; + var upward = ((right + 1) & 2) == 0; + var y = upward ? Size - 1 - vert : vert; + if (!IsFunction[y, x] && i < data.Length * 8) + { + Modules[y, x] = GetBit(data[i >> 3], 7 - (i & 7)); + i++; + } + } + } + } + } + + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // before masking. Due to the arithmetic of XOR, calling applyMask() with + // the same mask value a second time will undo the mask. A final well-formed + // QR Code needs exactly one (not zero, two, etc.) mask applied. + private void ApplyMask(int msk) + { + if (msk < 0 || msk > 7) + throw new ArgumentException("Mask value out of range"); + for (int y = 0; y < Size; y++) + { + for (int x = 0; x < Size; x++) + { + bool invert; + switch (msk) + { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: throw new InvalidOperationException(); + } + Modules[y, x] ^= invert & !IsFunction[y,x]; + } + } + } + + // A messy helper function for the constructor. This QR Code must be in an unmasked state when this + // method is called. The given argument is the requested mask, which is -1 for auto or 0 to 7 for fixed. + // This method applies and returns the actual mask chosen, from 0 to 7. + private int HandleConstructorMasking(int msk) + { + if (msk == -1) + { + var minPenalty = int.MaxValue; + for (int i = 0; i < 8; i++) + { + ApplyMask(i); + DrawFormatBits(i); + var penalty = GetPenaltyScore(); + if (penalty < minPenalty) + { + msk = i; + minPenalty = penalty; + } + ApplyMask(i); + } + } + + ApplyMask(msk); + DrawFormatBits(msk); + return msk; + } + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + private int GetPenaltyScore() + { + var result = 0; + + for (int y = 0; y < Size; y++) + { + var runColor = false; + var runX = 0; + var runHistory = new int[7]; + for (int x = 0; x < Size; x++) + { + if (Modules[y, x] == runColor) + { + runX++; + if (runX == 5) + result += PenaltyN1; + else if (runX > 5) + result++; + } + else + { + FinderPenaltyAddHistory(runX, runHistory); + if (!runColor) + result += FinderPenaltyCountPatterns(runHistory) * PenaltyN3; + runColor = Modules[y, x]; + runX = 1; + } + } + result += FinderPenaltyTerminateAndCount(runColor, runX, runHistory) * PenaltyN3; + } + + for (int x = 0; x < Size; x++) + { + var runColor = false; + var runY = 0; + var runHistory = new int[7]; + for (int y = 0; y < Size; y++) + { + if (Modules[y, x] == runColor) + { + runY++; + if (runY == 5) + result += PenaltyN1; + else if (runY > 5) + result++; + } + else + { + FinderPenaltyAddHistory(runY, runHistory); + if (!runColor) + result += FinderPenaltyCountPatterns(runHistory) * PenaltyN3; + runColor = Modules[y, x]; + runY = 1; + } + } + result += FinderPenaltyTerminateAndCount(runColor, runY, runHistory) * PenaltyN3; + } + + for (int y = 0; y < Size - 1; y++) + { + for (int x = 0; x < Size - 1; x++) + { + var color = Modules[y, x]; + if (color == Modules[y, x + 1] && + color == Modules[y + 1, x] && + color == Modules[y + 1, x + 1]) + result += PenaltyN2; + } + } + + var black = 0; + foreach (var color in Modules) + { + if (color) + black++; + } + var total = Size * Size; + + var k = (Math.Abs(black * 20 - total * 10) + total - 1) / total - 1; + result += k * PenaltyN4; + return result; + } + + #endregion + + #region Private helper functions + + // Returns an ascending list of positions of alignment patterns for this version number. + // Each position is in the range [0,177), and are used on both the x and y axes. + // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. + private int[] GetAlignmentPatternPositions() + { + if (Version == 1) + return new int[] { }; + else + { + var numAlign = Version / 7 + 2; + int step; + if (Version == 32) + step = 26; + else + step = (Version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; + var result = new int[numAlign]; + result[0] = 6; + for (int i = result.Length - 1, pos = Size - 7; i >= 1; i--, pos -= step) + result[i] = pos; + return result; + } + } + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after + // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. + private static int GetNumRawDataModules(int ver) + { + if (ver < MinVersion || ver > MaxVersion) + throw new ArgumentException("Version number out of range"); + + var size = ver * 4 + 17; + var result = size * size; + result -= 8 * 8 * 3; + result -= 15 * 2 + 1; + result -= (size - 16) * 2; + + if (ver >= 2) + { + var numAlign = ver / 7 + 2; + result -= (numAlign - 1) * (numAlign - 1) * 25; + result -= (numAlign - 2) * 2 * 20; + + if (ver >= 7) + result -= 6 * 3 * 2; + } + + return result; + } + + // Returns a Reed-Solomon ErrorCorrection generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + private static sbyte[] ReedSolomonComputeDivisor(int degree) + { + if (degree < 1 || degree > 255) + throw new ArgumentException("Degree out of range"); + var result = new sbyte[degree]; + result[degree - 1] = 1; + + var root = 1; + for (int i = 0; i < degree; i++) + { + for (int j = 0; j < result.Length; j++) + { + result[j] = (sbyte)ReedSolomonMultiply(result[j] & 0xFF, root); + if (j + 1 < result.Length) + result[j] ^= result[j + 1]; + } + root = ReedSolomonMultiply(root, 0x02); + } + return result; + } + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + private static sbyte[] ReedSolomonComputeRemainder(sbyte[] data, sbyte[] divisor) + { + var result = new sbyte[divisor.Length]; + foreach (sbyte b in data) + { + var factor = (b ^ result[0]) & 0xFF; + Array.Copy(result, 1, result, 0, result.Length - 1); + result[result.Length - 1] = 0; + for (int i = 0; i < result.Length; i++) + result[i] ^= (sbyte)ReedSolomonMultiply(divisor[i] & 0xFF, factor); + } + return result; + } + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). The arguments and result + // are unsigned 8-bit integers. This could be implemented as a lookup table of 256*256 entries of uint8. + private static int ReedSolomonMultiply(int x, int y) + { + var z = 0; + for (int i = 7; i >= 0; i--) + { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; + } + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. + static int GetNumDataCodewords(int ver, ErrorCorrection.Level ecl) + { + return GetNumRawDataModules(ver) / 8 + - ErrorCorrectionCodeWordsPerBlock[(int)ecl][ver] + * NumErrorCorrectionBlocks[(int)ecl][ver]; + } + + // Can only be called immediately after a white run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). + private int FinderPenaltyCountPatterns(int[] runHistory) + { + var n = runHistory[1]; + var core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n; + return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0); + } + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + private int FinderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, int[] runHistory) + { + if (currentRunColor) + { + FinderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += Size; + FinderPenaltyAddHistory(currentRunLength, runHistory); + return FinderPenaltyCountPatterns(runHistory); + } + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + private void FinderPenaltyAddHistory(int currentRunLength, int[] runHistory) + { + if (runHistory[0] == 0) + currentRunLength += Size; + Array.Copy(runHistory, 0, runHistory, 1, runHistory.Length - 1); + runHistory[0] = currentRunLength; + } + + // Returns true iff the i'th bit of x is set to 1. + public static bool GetBit(int x, int i) + { + return ((x >> i) & 1) != 0; + } + + #endregion + } +} diff --git a/csharp/QrCodeGenerator/QrCodeGenerator.csproj b/csharp/QrCodeGenerator/QrCodeGenerator.csproj new file mode 100644 index 0000000..1dc273d --- /dev/null +++ b/csharp/QrCodeGenerator/QrCodeGenerator.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + QrCodeGenerator + + + + + + + diff --git a/csharp/QrCodeGenerator/QrSegment.cs b/csharp/QrCodeGenerator/QrSegment.cs new file mode 100644 index 0000000..0b0bf78 --- /dev/null +++ b/csharp/QrCodeGenerator/QrSegment.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace QrCodeGenerator +{ + /// + /// A segment of character/binary/control data in a QR Code symbol. + /// Instances of this class are immutable. + /// The mid-level way to create a segment is to take the payload data and call a + /// static factory function such as .The low-level + /// way to create a segment is to custom-make the bit buffer and call the + /// with appropriate values. + /// This segment class imposes no length restrictions, but QR Codes have restrictions. + /// Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + /// Any segment longer than this is meaningless for the purpose of generating QR Codes. + /// + public class QrSegment + { + public BitBuffer Data { get; set; } + public int CharactersCount { get; set; } + public Mode Mode { get; set; } + + /// + /// Describes precisely all strings that are encodable in numeric mode. To test whether a + /// string s is encodable: bool ok = NumericRegex.Match(s).Success; . + /// A string is encodable iff each character is in the range 0 to 9. + /// + public static Regex NumericRegex = new Regex("^[0-9]*$"); + /// + /// Describes precisely all strings that are encodable in alphanumeric mode. To test whether a + /// string s is encodable: bool ok = AlphaNumericRegex.Match(s).Success; . + /// A string is encodable iff each character is in the following set: 0 to 9, A to Z + /// (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + /// + public static Regex AlphaNumericRegex = new Regex("^[A-Z0-9 $%*+./:-]*$"); + /// + /// The set of all legal characters in alphanumeric mode, where + /// each character value maps to the index in the string. + /// + public static string AlphaNumericCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + /// + /// Constructs a QR Code segment with the specified attributes and data. + /// The character count (numCh) must agree with the mode and the bit buffer length, + /// but the constraint isn't checked. The specified bit buffer is cloned and stored. + /// + /// the mode (not null) + /// the data length in characters or bytes, which is non-negative + /// the data bits (not null) + /// if the mode or data is null + /// if the character count is negative + public QrSegment(Mode mode, int charactersCount, BitBuffer data) + { + if (mode == null || data == null) throw new ArgumentNullException(""); + if (charactersCount < 0) throw new ArgumentException("Invalid value"); + + CharactersCount = charactersCount; + Data = data; + Mode = mode; + } + + /// + /// Returns a segment representing the specified binary data encoded in byte mode. All input byte arrays are acceptable. + ///

Any text string can be converted to UTF-8 bytes and encoded as a byte mode segment.

+ ///
+ /// the binary data (not null) + /// a segment (not null) containing the data + /// if the array is null + public static QrSegment MakeBytes(sbyte[] data) + { + if (data == null) throw new ArgumentNullException(""); + + var bitBuffer = new BitBuffer(); + foreach (var @byte in data) + { + bitBuffer.AppendBits(@byte & 0xFF, 8); + } + return new QrSegment(Mode.Byte, data.Length, bitBuffer); + } + + /// + /// Returns a segment representing the specified string of decimal digits encoded in numeric mode. + /// + /// the text (not null), with only digits from 0 to 9 allowed + /// a segment (not null) containing the text + /// if the string is null + /// if the string contains non-digit characters + public static QrSegment MakeNumeric(string digits) + { + if (digits == null) throw new ArgumentNullException(""); + if (!NumericRegex.Match(digits).Success) throw new ArgumentException("String contains non-numeric characters"); + + var bitBuffer = new BitBuffer(); + for (var index = 0; index < digits.Length;) + { + var n = Math.Min(digits.Length - index, 3); + var digitsSubstring = digits.Substring(index, n); + + bitBuffer.AppendBits(int.Parse(digitsSubstring), n * 3 + 1); + index += n; + } + return new QrSegment(Mode.Numeric, digits.Length, bitBuffer); + } + + /// + /// Returns a segment representing the specified text string encoded in alphanumeric mode. + /// The characters allowed are: 0 to 9, A to Z (uppercase only), space, + /// dollar, percent, asterisk, plus, hyphen, period, slash, colon. + /// + /// the text (not null), with only certain characters allowed + /// a segment (not {@code null}) containing the text + /// if the string is null + /// if the string contains non-encodable characters + public static QrSegment MakeAlphanumeric(string text) + { + if (text == null) throw new ArgumentNullException(""); + if (!AlphaNumericRegex.Match(text).Success) throw new ArgumentException("String contains unencodable characters in alphanumeric mode"); + + var bitBuffer = new BitBuffer(); + int index; + for (index = 0; index <= text.Length - 2; index += 2) + { + var temp = AlphaNumericCharset.IndexOf(text[index]) * 45; + temp += AlphaNumericCharset.IndexOf(text[index + 1]); + bitBuffer.AppendBits(temp, 11); + } + if (index < text.Length) + { + bitBuffer.AppendBits(AlphaNumericCharset.IndexOf(text[index]), 6); + } + return new QrSegment(Mode.AlphaNumeric, text.Length, bitBuffer); + } + + /// + /// Returns a list of zero or more segments to represent the specified Unicode text string. + /// The result may use various segment modes and switch modes to optimize the length of the bit stream. + /// + /// the text to be encoded, which can be any Unicode string + /// a new mutable list (not null) of segments (not null) containing the text + /// if the text is null + public static List MakeSegments(string text) + { + if (text == null) throw new ArgumentNullException(""); + + var result = new List(); + + if (text.Equals(string.Empty)) return result; + else if (NumericRegex.IsMatch(text)) + { + result.Add(MakeNumeric(text)); + return result; + } + else if (AlphaNumericRegex.Match(text).Success) + { + result.Add(MakeAlphanumeric(text)); + return result; + } + else + { + var bytes = Encoding.UTF8.GetBytes(text); + var sbytes = Array.ConvertAll(bytes, b => (sbyte)b); + result.Add(MakeBytes(sbytes)); + return result; + } + } + + /// + /// Returns a segment representing an Extended Channel Interpretation + /// (ECI) designator with the specified assignment value. + /// + /// the ECI assignment number (see the AIM ECI specification) + /// a segment (not null) containing the data + /// if the value is outside the range [0, 106) + public static QrSegment MakeEci(int assignVal) + { + var bitBuffer = new BitBuffer(); + + if (assignVal < 0) + { + throw new ArgumentException("ECI assignment value out of range"); + } + else if (assignVal < (1 << 7)) + { + bitBuffer.AppendBits(assignVal, 8); + return new QrSegment(Mode.Eci, 0, bitBuffer); + } + else if (assignVal < (1 << 14)) + { + bitBuffer.AppendBits(2, 2); + bitBuffer.AppendBits(assignVal, 14); + return new QrSegment(Mode.Eci, 0, bitBuffer); + } + else if (assignVal < 1_000_000) + { + bitBuffer.AppendBits(6, 3); + bitBuffer.AppendBits(assignVal, 21); + return new QrSegment(Mode.Eci, 0, bitBuffer); + } + else + { + throw new ArgumentException("ECI assignment value out of range"); + } + } + + /// + /// Returns the data bits of this segment. + /// + /// a new copy of the data bits (not null) + public BitBuffer GetData() + { + return Data.Clone(); + } + + /// + /// Calculates the number of bits needed to encode the given segments at the given version. + /// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too + /// many characters to fit its length field, or the total bits exceeds int.MaxValue. + /// + /// + /// + /// + public static int GetTotalBits(List segs, int version) + { + long result = 0; + foreach (QrSegment seg in segs) + { + var bits = seg.Mode.NumCharCountBits(version); + if (seg.CharactersCount >= (1 << bits)) + return -1; + result += 4L + bits + seg.Data.GetBitLength(); + if (result > int.MaxValue) + return -1; + } + return (int)result; + } + } + +}