diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index 3f3934d2..75a5a9d4 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -451,10 +451,10 @@ QRCodeData PlaceModules() var qr = new QRCodeData(version, true); var size = qr.ModuleMatrix.Count - 8; var tempBitArray = new BitArray(18); //version string requires 18 bits - using (var blockedModules = new ModulePlacer.BlockedModules(size)) + using (var blockedModules = new ModuleMatrix(size)) { ModulePlacer.PlaceFinderPatterns(qr, blockedModules); - ModulePlacer.ReserveSeperatorAreas(version, size, blockedModules); + ModulePlacer.ReserveSeparatorAreas(version, size, blockedModules); ModulePlacer.PlaceAlignmentPatterns(qr, AlignmentPatterns.FromVersion(version), blockedModules); ModulePlacer.PlaceTimingPatterns(qr, blockedModules); ModulePlacer.PlaceDarkModule(qr, version, blockedModules); @@ -462,7 +462,7 @@ QRCodeData PlaceModules() ModulePlacer.PlaceDataWords(qr, interleavedData, blockedModules); var maskVersion = ModulePlacer.MaskCode(qr, version, blockedModules, eccLevel); GetFormatString(tempBitArray, version, eccLevel, maskVersion); - ModulePlacer.PlaceFormat(qr, tempBitArray, true); + ModulePlacer.PlaceFormat(qr, tempBitArray); } if (version >= 7) diff --git a/QRCoder/QRCodeGenerator/ModuleMatrix.cs b/QRCoder/QRCodeGenerator/ModuleMatrix.cs new file mode 100644 index 00000000..0263632b --- /dev/null +++ b/QRCoder/QRCodeGenerator/ModuleMatrix.cs @@ -0,0 +1,467 @@ +#if HAS_SPAN +using System.Buffers; +using System.Buffers.Binary; +#endif + +using System.Diagnostics; + +namespace QRCoder; + +public partial class QRCodeGenerator +{ + private sealed class ModuleMatrix : IDisposable + { + public byte[] Bytes { get; } + public int Size { get; } + public int Stride { get; } + private int ByteLength { get; } + private bool Disposed { get; set; } + +#if !HAS_SPAN +#if NET40_OR_GREATER + private static readonly System.Collections.Concurrent.ConcurrentStack _pool = new(); +#else + private static byte[]? _pooledBytes; +#endif +#endif + + public ModuleMatrix(int size) + { + Debug.Assert(size > 0); + + Size = size; + + // Round up to number of bytes required per row and add one byte per row. + // The one byte extra will allow us to copy bit arrays with 4 bits of padding on each side in CopyFrom(QRCodeData data). + Stride = (size + 15) >> 3; + + ByteLength = Size * Stride; + +#if HAS_SPAN + Bytes = ArrayPool.Shared.Rent(ByteLength); + Bytes.AsSpan(0, ByteLength).Clear(); +#elif NET40_OR_GREATER + while (_pool.TryPop(out byte[]? bytes)) + { + // Test whether the array is large enough. + // If not, discard it and don't return it to the pool. When this instance is disposed, a larger array will be returned to the pool. + // Over time, the pool will tend to be filled with enough arrays that are large enough for the workload. + if (bytes.Length >= ByteLength) + { + Array.Clear(bytes, 0, ByteLength); + Bytes = bytes; + return; + } + } + + Bytes = new byte[ByteLength]; +#else + var bytes = Interlocked.Exchange(ref _pooledBytes, null); + if (bytes != null) + { + if (bytes.Length >= ByteLength) + { + Array.Clear(bytes, 0, ByteLength); + Bytes = bytes; + return; + } + else + { + Interlocked.Exchange(ref _pooledBytes, bytes); + } + } + + Bytes = new byte[ByteLength]; +#endif + } + + public bool this[int y, int x] + { + get + { + Debug.Assert((uint)y < (uint)Size); + Debug.Assert((uint)x < (uint)Size); + + return (GetByteRef(y, x) & (1 << (x & 7))) != 0; + } + set + { + Debug.Assert(!Disposed); + Debug.Assert((uint)y < (uint)Size); + Debug.Assert((uint)x < (uint)Size); + + int mask = 1 << (x & 7); + + ref byte target = ref GetByteRef(y, x); + + if (value) + { + target |= (byte)mask; + } + else + { + target &= (byte)~mask; + } + } + } + + private ref byte GetByteRef(int y, int x) => ref Bytes[y * Stride + (x >> 3)]; + + public void CopyFrom(QRCodeData data) + { + Debug.Assert(!Disposed); + Debug.Assert(Size == data.ModuleMatrix.Count - 8); + + var source = data.ModuleMatrix; + var target = Bytes; + +#if NETSTANDARD1_3 + Array.Clear(target, 0, target.Length); +#endif + + for (int y = 0; y < Size; y++) + { + var bitArray = source[y + 4]; + +#if NETSTANDARD1_3 + int startIndex = y * Stride; + + for (int x = 0; x < bitArray.Length - 8; x++) + { + if (bitArray[x + 4]) + { + target[startIndex + (x >> 3)] |= (byte)(1 << (x & 7)); + } + } +#else + bitArray.CopyTo(target, y * Stride); +#endif + } + +#if !NETSTANDARD1_3 + // Move all modules 4 places to the left +#if HAS_SPAN + if (BitConverter.IsLittleEndian) + { + var span = target.AsSpan(0, ByteLength); + + if (Environment.Is64BitProcess) + { + while (span.Length >= 8 + 1) + { + BinaryPrimitives.WriteUInt64LittleEndian(span, (BinaryPrimitives.ReadUInt64LittleEndian(span) >>> 4) | (((ulong)span[8]) << (64 - 4))); + span = span[8..]; + } + } + else + { + while (span.Length >= 4 + 1) + { + BinaryPrimitives.WriteUInt32LittleEndian(span, (BinaryPrimitives.ReadUInt32LittleEndian(span) >>> 4) | (((uint)span[4]) << (32 - 4))); + span = span[4..]; + } + } + + while (span.Length >= 2) + { + span[0] = (byte)((span[0] >> 4) | ((span[1] & 15) << (8 - 4))); + span = span[1..]; + } + + return; + } +#endif + for (int i = 1; i < ByteLength; i++) + { + target[i - 1] = (byte)((target[i - 1] >>> 4) | ((target[i] & 15) << (8 - 4))); + } +#endif + } + + public void SetModules(Rectangle rectangle) + { + Debug.Assert(!Disposed); + Debug.Assert((uint)rectangle.X < (uint)Size && (uint)rectangle.Y < (uint)Size); + Debug.Assert((uint)(rectangle.X + rectangle.Width) <= (uint)Size && (uint)(rectangle.Y + rectangle.Height) <= Size); + + for (int x = 0; x < rectangle.Width; x++) + { + var column = GetColumn(rectangle.X + x); + for (int y = 0; y < rectangle.Height; y++) + { + column[rectangle.Y + y] = true; + } + } + } + + public bool HasModulesSet(Rectangle rectangle) + { + Debug.Assert((uint)rectangle.X < (uint)Size && (uint)rectangle.Y < (uint)Size); + Debug.Assert((uint)(rectangle.X + rectangle.Width) <= (uint)Size && (uint)(rectangle.Y + rectangle.Height) <= Size); + + for (int x = 0; x < rectangle.Width; x++) + { + var column = GetColumn(rectangle.X + x); + for (int y = 0; y < rectangle.Height; y++) + { + if (column[rectangle.Y + y]) + { + return true; + } + } + } + + return false; + } + + /// + /// Calculates a penalty score for a Micro QR code to evaluate the effectiveness of a mask pattern. + /// A lower score indicates a QR code that is easier for decoders to read accurately. + /// + /// The total penalty score of the QR code. + public int ScoreMicro() + { + int size = Size; + int sum1 = 0; + int sum2 = 0; + for (int i = 1; i < size; i++) + { + if (this[size - 1, i]) + sum1++; + if (this[i, size - 1]) + sum2++; + } + int total = sum1 < sum2 ? sum1 * 16 + sum2 : sum2 * 16 + sum1; + return -total; // negate so that lower is better + } + + /// + /// Calculates a penalty score for a QR code to evaluate the effectiveness of a mask pattern. + /// A lower score indicates a QR code that is easier for decoders to read accurately. + /// The score is the sum of four penalty rules applied to the QR code. + /// + /// The total penalty score of the QR code. + public int Score() + { + int score = 0; + int blackModules = 0; + + int size = Size; + + // Penalty 1: Penalty for groups of five or more same-color modules in a row (or column) + for (int i = 0; i < size; i++) + { + var row = GetRow(i); + var column = GetColumn(i); + + int modInRow = 0, modInColumn = 0; + + bool lastValRow = false, lastValColumn = false; + + for (int j = 0; j < size; j++) + { + bool current = row[j]; + + if (current) + blackModules++; + + // Check rows for consecutive modules + if (current == lastValRow) + { + modInRow++; + if (modInRow == 5) + score += 3; + else if (modInRow > 5) + score++; + } + else + { + modInRow = 1; + } + + lastValRow = current; + + // Check columns for consecutive modules + current = column[j]; + + if (current == lastValColumn) + { + modInColumn++; + if (modInColumn == 5) + score += 3; + else if (modInColumn > 5) + score++; + } + else + { + modInColumn = 1; + } + + lastValColumn = current; + } + } + + // Penalty 4: Penalty for having more than 50% black modules or more than 50% white modules + int percentDiv5 = blackModules * 20 / (size * size); + int prevMultipleOf5 = Math.Abs(percentDiv5 - 10); + int nextMultipleOf5 = Math.Abs(percentDiv5 - 9); + score += Math.Min(prevMultipleOf5, nextMultipleOf5) * 10; + + // Penalty 2: Penalty for square blocks of four modules in the same color + for (int y = 0; y < size - 1; y++) + { + var currentRow = GetRow(y); + var nextRow = GetRow(y + 1); + + for (int x = 0; x < size - 1; x++) + { + bool topRightModule = currentRow[x + 1]; + + if (topRightModule != nextRow[x + 1]) + { + // If the right modules don't match, skip the next column. + // They wouldn't match being the left modules either. + x++; + } + else if (topRightModule == currentRow[x] && topRightModule == nextRow[x]) + { + score += 3; + } + } + } + + // Penalty 3: Penalty for specific patterns within the QR code (patterns that should be avoided) + for (int i = 0; i < size; i++) + { + // Horizontal pattern matching + var r = GetRow(i); + + for (int j = 0; j < size - 10; j++) + { + if (r[j + 6]) + { + if (!r[j + 1] && r[j + 4] && !r[j + 5] && !r[j + 9]) + { + if (r[j] && r[j + 2] && r[j + 3] && !r[j + 7] && !r[j + 8] && !r[j + 10] || + !r[j] && !r[j + 2] && !r[j + 3] && r[j + 7] && r[j + 8] && r[j + 10]) + { + score += 40; + } + } + + // If r[j + 6] was set, then r[j + 5] would be set when we move one column. + j++; + } + } + + // Vertical pattern matching + var c = GetColumn(i); + + for (int j = 0; j < size - 10; j++) + { + if (c[j + 6]) + { + if (!c[j + 1] && c[j + 4] && !c[j + 5] && !c[j + 9]) + { + if (c[j] && c[j + 2] && c[j + 3] && !c[j + 7] && !c[j + 8] && !c[j + 10] || + !c[j] && !c[j + 2] && !c[j + 3] && c[j + 7] && c[j + 8] && c[j + 10]) + { + score += 40; + } + } + + // If c[j + 6] was set, then c[j + 5] would be set when we move one row. + j++; + } + } + } + + return score; + } + + public void Dispose() + { + Debug.Assert(!Disposed); + Disposed = true; + +#if HAS_SPAN + // Avoid leaking QR code data into the pool + Bytes.AsSpan(0, ByteLength).Clear(); + ArrayPool.Shared.Return(Bytes); +#elif NET40_OR_GREATER + _pool.Push(Bytes); +#else + Interlocked.Exchange(ref _pooledBytes, Bytes); +#endif + } + + public Row GetRow(int y) => new Row(this, y); + + public Column GetColumn(int x) => new Column(this, x); + + public readonly struct Row + { + private readonly ModuleMatrix _moduleMatrix; + + private readonly int _startByteIndex; + + public Row(ModuleMatrix moduleMatrix, int y) + { + Debug.Assert((uint)y < (uint)moduleMatrix.Size); + _moduleMatrix = moduleMatrix; + _startByteIndex = moduleMatrix.Stride * y; + } + + public bool this[int x] + { + get + { + Debug.Assert((uint)x < (uint)_moduleMatrix.Size); + + return (_moduleMatrix.Bytes[_startByteIndex + (x >> 3)] & (1 << (x & 7))) != 0; + } + } + } + + public readonly struct Column + { + private readonly ModuleMatrix _moduleMatrix; + private readonly int _byteIndex; + private readonly byte _byteMask; + + public Column(ModuleMatrix moduleMatrix, int x) + { + Debug.Assert((uint)x < (uint)moduleMatrix.Size); + _moduleMatrix = moduleMatrix; + _byteIndex = x >> 3; + _byteMask = (byte)(1 << (x & 7)); + } + + public bool this[int y] + { + get + { + Debug.Assert((uint)y < (uint)_moduleMatrix.Size); + + return (_moduleMatrix.Bytes[y * _moduleMatrix.Stride + _byteIndex] & _byteMask) != 0; + } + set + { + Debug.Assert(!_moduleMatrix.Disposed); + Debug.Assert((uint)y < (uint)_moduleMatrix.Size); + + ref byte target = ref _moduleMatrix.Bytes[y * _moduleMatrix.Stride + _byteIndex]; + + if (value) + { + target |= _byteMask; + } + else + { + target &= (byte)~_byteMask; + } + } + } + } + } +} diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.BlockedModules.cs b/QRCoder/QRCodeGenerator/ModulePlacer.BlockedModules.cs deleted file mode 100644 index b865e8cc..00000000 --- a/QRCoder/QRCodeGenerator/ModulePlacer.BlockedModules.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace QRCoder; - -public partial class QRCodeGenerator -{ - private static partial class ModulePlacer - { - /// - /// Struct that represents blocked modules using rectangles. - /// - public struct BlockedModules : IDisposable - { - private readonly BitArray[] _blockedModules; - - private static BitArray[]? _staticBlockedModules; - - /// - /// Initializes a new instance of the struct with a specified size. - /// - /// The size of the blocked modules matrix. - public BlockedModules(int size) - { - _blockedModules = Interlocked.Exchange(ref _staticBlockedModules, null)!; - if (_blockedModules != null && _blockedModules.Length >= size) - { - for (int i = 0; i < size; i++) - _blockedModules[i].SetAll(false); - } - else - { - _blockedModules = new BitArray[size]; - for (int i = 0; i < size; i++) - _blockedModules[i] = new BitArray(size); - } - } - - /// - /// Adds a blocked module at the specified coordinates. - /// - /// The x-coordinate of the module. - /// The y-coordinate of the module. - public void Add(int x, int y) - => _blockedModules[y][x] = true; - - /// - /// Adds a blocked module defined by the specified rectangle. - /// - /// The rectangle that defines the blocked module. - public void Add(Rectangle rect) - { - for (int y = rect.Y; y < rect.Y + rect.Height; y++) - { - for (int x = rect.X; x < rect.X + rect.Width; x++) - { - _blockedModules[y][x] = true; - } - } - } - - /// - /// Checks if the specified coordinates are blocked. - /// - /// The x-coordinate to check. - /// The y-coordinate to check. - /// true if the coordinates are blocked; otherwise, false. - public bool IsBlocked(int x, int y) - => _blockedModules[y][x]; - - /// - /// Checks if the specified rectangle is blocked. - /// - /// The rectangle to check. - /// true if the rectangle is blocked; otherwise, false. - public bool IsBlocked(Rectangle r1) - { - for (int y = r1.Y; y < r1.Y + r1.Height; y++) - { - for (int x = r1.X; x < r1.X + r1.Width; x++) - { - if (_blockedModules[y][x]) - return true; - } - } - return false; - } - - public void Dispose() - => Interlocked.CompareExchange(ref _staticBlockedModules, _blockedModules, null); - } - } -} diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs index b702e6e2..2f78bcaa 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace QRCoder; public partial class QRCodeGenerator @@ -5,233 +7,135 @@ public partial class QRCodeGenerator private static partial class ModulePlacer { /// - /// Provides static methods and properties to handle mask patterns used in QR code generation. + /// Provides the data to handle mask patterns used in QR code generation. /// Mask patterns are applied to QR codes to break up patterns in the data matrix that might confuse scanners. /// - private static class MaskPattern + private readonly struct MaskPattern { /// - /// A dictionary mapping each mask pattern index to its corresponding function that calculates whether a given pixel should be masked. + /// An array mapping each mask pattern index to its corresponding pattern that determines whether a given pixel should be masked. /// - public static readonly List> Patterns = - new List>(8) { - MaskPattern.Pattern1, MaskPattern.Pattern2, MaskPattern.Pattern3, MaskPattern.Pattern4, - MaskPattern.Pattern5, MaskPattern.Pattern6, MaskPattern.Pattern7, MaskPattern.Pattern8 - }; + public static readonly MaskPattern[] Patterns = + [Pattern1, Pattern2, Pattern3, Pattern4, Pattern5, Pattern6, Pattern7, Pattern8]; /// /// Mask pattern 1: (x + y) % 2 == 0 /// Applies a checkerboard mask on the QR code. /// - public static bool Pattern1(int x, int y) - => (x + y) % 2 == 0; + private static MaskPattern Pattern1 => new MaskPattern((int x, int y) => (x + y) % 2 == 0); /// /// Mask pattern 2: y % 2 == 0 /// Applies a horizontal striping mask on the QR code. /// - public static bool Pattern2(int x, int y) - => y % 2 == 0; + private static MaskPattern Pattern2 => new MaskPattern((int x, int y) => y % 2 == 0); /// /// Mask pattern 3: x % 3 == 0 /// Applies a vertical striping mask on the QR code. /// - public static bool Pattern3(int x, int y) - => x % 3 == 0; + private static MaskPattern Pattern3 => new MaskPattern((int x, int y) => x % 3 == 0); /// /// Mask pattern 4: (x + y) % 3 == 0 /// Applies a diagonal striping mask on the QR code. /// - public static bool Pattern4(int x, int y) - => (x + y) % 3 == 0; + private static MaskPattern Pattern4 => new MaskPattern((int x, int y) => (x + y) % 3 == 0); /// /// Mask pattern 5: ((y / 2) + (x / 3)) % 2 == 0 /// Applies a complex pattern mask on the QR code, mixing horizontal and vertical rules. /// - public static bool Pattern5(int x, int y) - => ((int)(Math.Floor(y / 2d) + Math.Floor(x / 3d)) % 2) == 0; + private static MaskPattern Pattern5 => new MaskPattern((int x, int y) => ((y / 2) + (x / 3)) % 2 == 0); /// /// Mask pattern 6: ((x * y) % 2 + (x * y) % 3) == 0 /// Applies a mask based on the product of x and y coordinates modulo 2 and 3. /// - public static bool Pattern6(int x, int y) - => ((x * y) % 2) + ((x * y) % 3) == 0; + private static MaskPattern Pattern6 => new MaskPattern((int x, int y) => ((x * y) % 2) + ((x * y) % 3) == 0); /// /// Mask pattern 7: (((x * y) % 2 + (x * y) % 3) % 2) == 0 /// Applies a mask based on a more complex function involving the product of x and y coordinates. /// - public static bool Pattern7(int x, int y) - => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0; + private static MaskPattern Pattern7 => new MaskPattern((int x, int y) => (((x * y) % 2) + ((x * y) % 3)) % 2 == 0); /// /// Mask pattern 8: (((x + y) % 2) + ((x * y) % 3) % 2) == 0 /// Combines rules of checkers and complex multiplicative masks. /// - public static bool Pattern8(int x, int y) - => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0; + private static MaskPattern Pattern8 => new MaskPattern((int x, int y) => (((x + y) % 2) + ((x * y) % 3)) % 2 == 0); - /// - /// Calculates a penalty score for a Micro QR code to evaluate the effectiveness of a mask pattern. - /// A lower score indicates a QR code that is easier for decoders to read accurately. - /// - /// The QR code data structure to be evaluated. - /// The total penalty score of the QR code. - public static int ScoreMicro(QRCodeData qrCode) - { - int size = qrCode.ModuleMatrix.Count; - int sum1 = 0; - int sum2 = 0; - for (int i = 1; i < size; i++) - { - if (qrCode.ModuleMatrix[size - 1][i]) - sum1++; - if (qrCode.ModuleMatrix[i][size - 1]) - sum2++; - } - int total = sum1 < sum2 ? sum1 * 16 + sum2 : sum2 * 16 + sum1; - return -total; // negate so that lower is better - } + private readonly byte[] _bytes; - /// - /// Calculates a penalty score for a QR code to evaluate the effectiveness of a mask pattern. - /// A lower score indicates a QR code that is easier for decoders to read accurately. - /// The score is the sum of four penalty rules applied to the QR code. - /// - /// The QR code data structure to be evaluated. - /// The total penalty score of the QR code. - public static int Score(QRCodeData qrCode) + public Func Generator { get; } + + private const int PATTERN_SIZE = 12; + + public MaskPattern(Func generator) { - int score1 = 0, // Penalty for groups of five or more same-color modules in a row (or column) - score2 = 0, // Penalty for blocks of modules in the same color - score3 = 0, // Penalty for specific patterns found within the QR code - score4 = 0; // Penalty for having more than 50% black modules or more than 50% white modules - var size = qrCode.ModuleMatrix.Count; - - //Penalty 1: Checking for consecutive modules of the same color in rows and columns - for (var y = 0; y < size; y++) + Generator = generator; + + var bytes = new byte[2 * PATTERN_SIZE * PATTERN_SIZE / 8]; + for (int x = 0; x < PATTERN_SIZE; x++) { - var modInRow = 0; - var modInColumn = 0; - var lastValRow = qrCode.ModuleMatrix[y][0]; - var lastValColumn = qrCode.ModuleMatrix[0][y]; - for (var x = 0; x < size; x++) + int byteIndex1 = x >> 3; + byte mask1 = (byte)(1 << (x & 7)); + + int byteIndex2 = (PATTERN_SIZE + x) >> 3; + byte mask2 = (byte)(1 << ((PATTERN_SIZE + x) & 7)); + + for (int y = 0; y < PATTERN_SIZE; y++) { - // Check rows for consecutive modules - if (qrCode.ModuleMatrix[y][x] == lastValRow) - modInRow++; - else - modInRow = 1; - if (modInRow == 5) - score1 += 3; - else if (modInRow > 5) - score1++; - lastValRow = qrCode.ModuleMatrix[y][x]; - - // Check columns for consecutive modules - if (qrCode.ModuleMatrix[x][y] == lastValColumn) - modInColumn++; - else - modInColumn = 1; - if (modInColumn == 5) - score1 += 3; - else if (modInColumn > 5) - score1++; - lastValColumn = qrCode.ModuleMatrix[x][y]; + if (generator(x, y)) + { + bytes[y * (2 * PATTERN_SIZE / 8) + byteIndex1] |= mask1; + bytes[y * (2 * PATTERN_SIZE / 8) + byteIndex2] |= mask2; + } } } - //Penalty 2: Checking for blocks of modules in the same color - for (var y = 0; y < size - 1; y++) + _bytes = bytes; + } + + public void Apply(ModuleMatrix moduleMatrix, ModuleMatrix blockedModules) + { + Debug.Assert(moduleMatrix.Size == blockedModules.Size); + Debug.Assert(moduleMatrix.Stride == blockedModules.Stride); + + var pattern = _bytes; + var target = moduleMatrix.Bytes; + var blocked = blockedModules.Bytes; + + for (int y = 0; y < moduleMatrix.Size; y++) { - for (var x = 0; x < size - 1; x++) + int rowStartIndex = y * moduleMatrix.Stride; + + byte byte0 = pattern[y % PATTERN_SIZE * 3 + 0]; + byte byte1 = pattern[y % PATTERN_SIZE * 3 + 1]; + byte byte2 = pattern[y % PATTERN_SIZE * 3 + 2]; + + int bx = 0; + + for (; bx + 3 <= moduleMatrix.Stride; bx += 3) { - if (qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y][x + 1] && - qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x] && - qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x + 1]) - score2 += 3; + target[rowStartIndex + bx + 0] ^= (byte)(byte0 & ~blocked[rowStartIndex + bx + 0]); + target[rowStartIndex + bx + 1] ^= (byte)(byte1 & ~blocked[rowStartIndex + bx + 1]); + target[rowStartIndex + bx + 2] ^= (byte)(byte2 & ~blocked[rowStartIndex + bx + 2]); } - } - //Penalty 3: Checking for specific patterns within the QR code (patterns that should be avoided) - for (var y = 0; y < size; y++) - { - for (var x = 0; x < size - 10; x++) + if (bx < moduleMatrix.Stride) { - // Horizontal pattern matching - if ((qrCode.ModuleMatrix[y][x] && - !qrCode.ModuleMatrix[y][x + 1] && - qrCode.ModuleMatrix[y][x + 2] && - qrCode.ModuleMatrix[y][x + 3] && - qrCode.ModuleMatrix[y][x + 4] && - !qrCode.ModuleMatrix[y][x + 5] && - qrCode.ModuleMatrix[y][x + 6] && - !qrCode.ModuleMatrix[y][x + 7] && - !qrCode.ModuleMatrix[y][x + 8] && - !qrCode.ModuleMatrix[y][x + 9] && - !qrCode.ModuleMatrix[y][x + 10]) || - (!qrCode.ModuleMatrix[y][x] && - !qrCode.ModuleMatrix[y][x + 1] && - !qrCode.ModuleMatrix[y][x + 2] && - !qrCode.ModuleMatrix[y][x + 3] && - qrCode.ModuleMatrix[y][x + 4] && - !qrCode.ModuleMatrix[y][x + 5] && - qrCode.ModuleMatrix[y][x + 6] && - qrCode.ModuleMatrix[y][x + 7] && - qrCode.ModuleMatrix[y][x + 8] && - !qrCode.ModuleMatrix[y][x + 9] && - qrCode.ModuleMatrix[y][x + 10])) - { - score3 += 40; - } + target[rowStartIndex + bx] ^= (byte)(byte0 & ~blocked[rowStartIndex + bx]); - // Vertical pattern matching - if ((qrCode.ModuleMatrix[x][y] && - !qrCode.ModuleMatrix[x + 1][y] && - qrCode.ModuleMatrix[x + 2][y] && - qrCode.ModuleMatrix[x + 3][y] && - qrCode.ModuleMatrix[x + 4][y] && - !qrCode.ModuleMatrix[x + 5][y] && - qrCode.ModuleMatrix[x + 6][y] && - !qrCode.ModuleMatrix[x + 7][y] && - !qrCode.ModuleMatrix[x + 8][y] && - !qrCode.ModuleMatrix[x + 9][y] && - !qrCode.ModuleMatrix[x + 10][y]) || - (!qrCode.ModuleMatrix[x][y] && - !qrCode.ModuleMatrix[x + 1][y] && - !qrCode.ModuleMatrix[x + 2][y] && - !qrCode.ModuleMatrix[x + 3][y] && - qrCode.ModuleMatrix[x + 4][y] && - !qrCode.ModuleMatrix[x + 5][y] && - qrCode.ModuleMatrix[x + 6][y] && - qrCode.ModuleMatrix[x + 7][y] && - qrCode.ModuleMatrix[x + 8][y] && - !qrCode.ModuleMatrix[x + 9][y] && - qrCode.ModuleMatrix[x + 10][y])) + bx++; + + if (bx < moduleMatrix.Stride) { - score3 += 40; + target[rowStartIndex + bx] ^= (byte)(byte1 & ~blocked[rowStartIndex + bx]); } } } - - //Penalty 4: Proportions of dark and light modules - int blackModules = 0; - foreach (var bitArray in qrCode.ModuleMatrix) - for (var x = 0; x < size; x++) - if (bitArray[x]) - blackModules++; - - var percentDiv5 = blackModules * 20 / (qrCode.ModuleMatrix.Count * qrCode.ModuleMatrix.Count); - var prevMultipleOf5 = Math.Abs(percentDiv5 - 10); - var nextMultipleOf5 = Math.Abs(percentDiv5 - 9); - score4 = Math.Min(prevMultipleOf5, nextMultipleOf5) * 10; - - // Return the sum of all four penalties - return (score1 + score2) + (score3 + score4); } } } diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs index 771cd8ae..954460b0 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace QRCoder; public partial class QRCodeGenerator @@ -28,17 +30,29 @@ public static void PlaceVersion(QRCodeData qrCode, BitArray versionStr, bool off } } + public static void PlaceVersion(ModuleMatrix qrCode, BitArray versionStr) + { + var size = qrCode.Size; + + // Loop through each module position intended for version information, placed adjacent to the separators. + for (var x = 0; x < 6; x++) + { + for (var y = 0; y < 3; y++) + { + // Apply the version bits to the corresponding modules on the matrix, mapping the bits from the versionStr array. + qrCode[y + size - 11, x] = qrCode[x, y + size - 11] = versionStr[17 - (x * 3 + y)]; + } + } + } + /// /// Places the format information on the QR code, encoding the error correction level and mask pattern used. /// /// The QR code data structure to modify. /// The bit array containing the format information. - /// Specifies whether an offset should be applied. - public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offset) + public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr) { - var isMicro = qrCode.Version < 0; // Negative versions indicate Micro QR codes. - var offsetValue = offset ? 4 : 0; - var size = qrCode.ModuleMatrix.Count - offsetValue - offsetValue; + const int offsetValue = 4; // Standard QR Code Format Positions: // @@ -83,33 +97,69 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse // The bit pattern is considered an entire 'word' and LSB goes in position 0 // So, we need to reverse the order of the generated bit pattern, hence the (14 - i) below - for (var i = 0; i < 15; i++) + // Negative versions indicate Micro QR codes. + if (qrCode.Version < 0) { - int x1, y1, x2, y2; - - if (isMicro) + for (int i = 0; i < 15; i++) { // Micro QR format positions - x1 = i < 8 ? 8 : 14 - i + 1; - y1 = i < 8 ? i + 1 : 8; + int x1 = i < 8 ? 8 : 14 - i + 1; + int y1 = i < 8 ? i + 1 : 8; // Micro QR only uses one set of format positions, no duplication. qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i]; } - else + } + else + { + int size = qrCode.ModuleMatrix.Count - 2 * offsetValue; + + for (int i = 0; i < 15; i++) { // Standard QR format positions - x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i; - y1 = i < 6 ? i : i < 7 ? i + 1 : 8; - x2 = i < 8 ? size - 1 - i : 8; - y2 = i < 8 ? 8 : size - (15 - i); + int x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i; + int y1 = i < 6 ? i : i < 7 ? i + 1 : 8; + int x2 = i < 8 ? size - 1 - i : 8; + int y2 = i < 8 ? 8 : size - (15 - i); - qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = formatStr[14 - i]; + qrCode.ModuleMatrix[y1 + offsetValue][x1 + offsetValue] = qrCode.ModuleMatrix[y2 + offsetValue][x2 + offsetValue] = formatStr[14 - i]; } } } + public static void PlaceFormat(ModuleMatrix qrCode, int version, BitArray formatStr) + { + // Negative versions indicate Micro QR codes. + if (version < 0) + { + for (int i = 0; i < 15; i++) + { + // Micro QR format positions + int x1 = i < 8 ? 8 : 14 - i + 1; + int y1 = i < 8 ? i + 1 : 8; + + // Micro QR only uses one set of format positions, no duplication. + qrCode[y1, x1] = formatStr[14 - i]; + } + } + else + { + int size = qrCode.Size; + + for (int i = 0; i < 15; i++) + { + // Standard QR format positions + int x1 = i < 8 ? 8 : i == 8 ? 7 : 14 - i; + int y1 = i < 6 ? i : i < 7 ? i + 1 : 8; + int x2 = i < 8 ? size - 1 - i : 8; + int y2 = i < 8 ? 8 : size - (15 - i); + + qrCode[y1, x1] = qrCode[y2, x2] = formatStr[14 - i]; + } + } + } + /// /// Applies the most effective mask pattern to the QR code based on minimizing the penalty score, /// which evaluates how well the pattern will work for QR scanners. @@ -119,67 +169,47 @@ public static void PlaceFormat(QRCodeData qrCode, BitArray formatStr, bool offse /// List of rectangles representing areas that must not be overwritten. /// The error correction level of the QR code, which affects format string values. /// The index of the selected mask pattern. - public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blockedModules, ECCLevel eccLevel) + public static int MaskCode(QRCodeData qrCode, int version, ModuleMatrix blockedModules, ECCLevel eccLevel) { int selectedPattern = -1; // no pattern selected yet var patternScore = int.MaxValue; // lower score is better var size = qrCode.ModuleMatrix.Count - 8; - // Temporary QRCodeData object to test different mask patterns without altering the original. - var qrTemp = new QRCodeData(version, false); BitArray? versionString = null; if (version >= 7) { versionString = new BitArray(18); GetVersionString(versionString, version); } + + // Temporary QRCodeData object to test different mask patterns without altering the original. + using var copy = new ModuleMatrix(qrCode.ModuleMatrix.Count - 8); + var formatStr = new BitArray(15); for (var maskPattern = 0; maskPattern < 8; maskPattern++) { if (version < 0 && (maskPattern == 0 || maskPattern == 2 || maskPattern == 3 || maskPattern == 5)) continue; // Micro QR codes only support certain mask patterns. - var patternFunc = MaskPattern.Patterns[maskPattern]; - // Reset the temporary QR code to the current state of the actual QR code. - for (var y = 0; y < size; y++) - { - for (var x = 0; x < size; x++) - { - qrTemp.ModuleMatrix[y][x] = qrCode.ModuleMatrix[y + 4][x + 4]; - } - } + copy.CopyFrom(qrCode); // Place format information using the current mask pattern. GetFormatString(formatStr, version, eccLevel, maskPattern); - ModulePlacer.PlaceFormat(qrTemp, formatStr, false); + PlaceFormat(copy, version, formatStr); // Place version information if applicable. if (versionString != null) // aka if (version >= 7) { - ModulePlacer.PlaceVersion(qrTemp, versionString, false); + PlaceVersion(copy, versionString); } // Apply the mask pattern and calculate the score. - for (var x = 0; x < size; x++) - { - for (var y = 0; y < x; y++) - { - if (!blockedModules.IsBlocked(x, y)) - { - qrTemp.ModuleMatrix[y][x] ^= patternFunc(x, y); - qrTemp.ModuleMatrix[x][y] ^= patternFunc(y, x); - } - } - - if (!blockedModules.IsBlocked(x, x)) - { - qrTemp.ModuleMatrix[x][x] ^= patternFunc(x, x); - } - } + var pattern = MaskPattern.Patterns[maskPattern]; + pattern.Apply(copy, blockedModules); - var score = version < 0 ? MaskPattern.ScoreMicro(qrTemp) : MaskPattern.Score(qrTemp); + int score = version < 0 ? copy.ScoreMicro() : copy.Score(); // Select the pattern with the lowest score, indicating better QR code readability. if (patternScore > score) @@ -190,19 +220,20 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke } // Apply the best mask pattern to the actual QR code. - var selectedPatternFunc = MaskPattern.Patterns[selectedPattern]; + var selectedPatternFunc = MaskPattern.Patterns[selectedPattern].Generator; for (var x = 0; x < size; x++) { for (var y = 0; y < x; y++) { - if (!blockedModules.IsBlocked(x, y)) + Debug.Assert(blockedModules[y, x] == blockedModules[x, y]); + if (!blockedModules[x, y]) { qrCode.ModuleMatrix[y + 4][x + 4] ^= selectedPatternFunc(x, y); qrCode.ModuleMatrix[x + 4][y + 4] ^= selectedPatternFunc(y, x); } } - if (!blockedModules.IsBlocked(x, x)) + if (!blockedModules[x, x]) { qrCode.ModuleMatrix[x + 4][x + 4] ^= selectedPatternFunc(x, x); } @@ -217,7 +248,7 @@ public static int MaskCode(QRCodeData qrCode, int version, BlockedModules blocke /// The QR code data structure where the data bits are to be placed. /// The data bits to be placed within the QR code matrix. /// A list of rectangles representing areas within the QR code matrix that should not be modified because they contain other necessary information like format and version info. - public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModules blockedModules) + public static void PlaceDataWords(QRCodeData qrCode, BitArray data, ModuleMatrix blockedModules) { var size = qrCode.ModuleMatrix.Count - 8; // Get the size of the QR code matrix. var up = true; // A boolean flag used to alternate the direction of filling data: up or down. @@ -238,9 +269,9 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul int y = up ? size - yMod : yMod - 1; // Place data if within data length and current position is not blocked. - if (index < count && !blockedModules.IsBlocked(x, y)) + if (index < count && !blockedModules[x, y]) qrCode.ModuleMatrix[y + 4][x + 4] = data[index++]; - if (index < count && x > 0 && !blockedModules.IsBlocked(x - 1, y)) + if (index < count && x > 0 && !blockedModules[x - 1, y]) qrCode.ModuleMatrix[y + 4][x - 1 + 4] = data[index++]; } @@ -255,20 +286,20 @@ public static void PlaceDataWords(QRCodeData qrCode, BitArray data, BlockedModul /// The version of the QR code, which determines the number of finder patterns. /// The size of the QR code matrix. /// A list of rectangles representing areas that must not be overwritten. - public static void ReserveSeperatorAreas(int version, int size, BlockedModules blockedModules) + public static void ReserveSeparatorAreas(int version, int size, ModuleMatrix blockedModules) { // Block areas around the top-left finder pattern - blockedModules.Add(new Rectangle(7, 0, 1, 8)); // Vertical block near the top left finder pattern - blockedModules.Add(new Rectangle(0, 7, 7, 1)); // Horizontal block near the top left finder pattern + blockedModules.SetModules(new Rectangle(7, 0, 1, 8)); // Vertical block near the top left finder pattern + blockedModules.SetModules(new Rectangle(0, 7, 7, 1)); // Horizontal block near the top left finder pattern if (version > 0) // Non-micro QR codes have 3 finder patterns { // Block areas around the bottom-left finder pattern - blockedModules.Add(new Rectangle(0, size - 8, 8, 1)); // Horizontal block near the bottom left finder pattern - blockedModules.Add(new Rectangle(7, size - 7, 1, 7)); // Vertical block near the bottom left finder pattern + blockedModules.SetModules(new Rectangle(0, size - 8, 8, 1)); // Horizontal block near the bottom left finder pattern + blockedModules.SetModules(new Rectangle(7, size - 7, 1, 7)); // Vertical block near the bottom left finder pattern // Block areas around the top-right finder pattern - blockedModules.Add(new Rectangle(size - 8, 0, 1, 8)); // Vertical block near the top right finder pattern - blockedModules.Add(new Rectangle(size - 7, 7, 7, 1)); // Horizontal block near the top right finder pattern + blockedModules.SetModules(new Rectangle(size - 8, 0, 1, 8)); // Vertical block near the top right finder pattern + blockedModules.SetModules(new Rectangle(size - 7, 7, 7, 1)); // Horizontal block near the top right finder pattern } } @@ -278,28 +309,28 @@ public static void ReserveSeperatorAreas(int version, int size, BlockedModules b /// The size of the QR code matrix. /// The version number of the QR code, which determines the placement of version information. /// A list of rectangles representing areas that must not be overwritten. - public static void ReserveVersionAreas(int size, int version, BlockedModules blockedModules) + public static void ReserveVersionAreas(int size, int version, ModuleMatrix blockedModules) { if (version < 0) // Micro QR codes { - blockedModules.Add(new Rectangle(0, 8, 9, 1)); - blockedModules.Add(new Rectangle(8, 0, 1, 8)); + blockedModules.SetModules(new Rectangle(0, 8, 9, 1)); + blockedModules.SetModules(new Rectangle(8, 0, 1, 8)); return; } // Reserve areas near the timing patterns for version and format information. - blockedModules.Add(new Rectangle(8, 0, 1, 6)); // Near the top timing pattern - blockedModules.Add(new Rectangle(8, 7, 1, 1)); // Small square near the top left finder pattern - blockedModules.Add(new Rectangle(0, 8, 6, 1)); // Near the left timing pattern - blockedModules.Add(new Rectangle(7, 8, 2, 1)); // Extension of the above block - blockedModules.Add(new Rectangle(size - 8, 8, 8, 1)); // Near the right timing pattern - blockedModules.Add(new Rectangle(8, size - 7, 1, 7)); // Near the bottom timing pattern + blockedModules.SetModules(new Rectangle(8, 0, 1, 6)); // Near the top timing pattern + blockedModules.SetModules(new Rectangle(8, 7, 1, 1)); // Small square near the top left finder pattern + blockedModules.SetModules(new Rectangle(0, 8, 6, 1)); // Near the left timing pattern + blockedModules.SetModules(new Rectangle(7, 8, 2, 1)); // Extension of the above block + blockedModules.SetModules(new Rectangle(size - 8, 8, 8, 1)); // Near the right timing pattern + blockedModules.SetModules(new Rectangle(8, size - 7, 1, 7)); // Near the bottom timing pattern // If the version is 7 or higher, additional blocks for version information are added. if (version >= 7) { - blockedModules.Add(new Rectangle(size - 11, 0, 3, 6)); // Top right version information block - blockedModules.Add(new Rectangle(0, size - 11, 6, 3)); // Bottom left version information block + blockedModules.SetModules(new Rectangle(size - 11, 0, 3, 6)); // Top right version information block + blockedModules.SetModules(new Rectangle(0, size - 11, 6, 3)); // Bottom left version information block } } @@ -309,7 +340,7 @@ public static void ReserveVersionAreas(int size, int version, BlockedModules blo /// The QR code data structure where the dark module is to be placed. /// The version number of the QR code, which determines the specific location of the dark module. /// A list of rectangles representing areas that must not be overwritten, updated to include the dark module. - public static void PlaceDarkModule(QRCodeData qrCode, int version, BlockedModules blockedModules) + public static void PlaceDarkModule(QRCodeData qrCode, int version, ModuleMatrix blockedModules) { // Micro QR codes do not have a dark module if (version < 0) @@ -317,7 +348,7 @@ public static void PlaceDarkModule(QRCodeData qrCode, int version, BlockedModule // Place the dark module, which is always required to be black. qrCode.ModuleMatrix[4 * version + 9 + 4][8 + 4] = true; // Block the dark module area to prevent overwriting during further QR code generation steps. - blockedModules.Add(new Rectangle(8, 4 * version + 9, 1, 1)); + blockedModules[4 * version + 9, 8] = true; } /// @@ -325,7 +356,7 @@ public static void PlaceDarkModule(QRCodeData qrCode, int version, BlockedModule /// /// The QR code data structure where the finder patterns will be placed. /// A list of rectangles representing areas that must not be overwritten. This is updated with the areas occupied by the finder patterns. - public static void PlaceFinderPatterns(QRCodeData qrCode, BlockedModules blockedModules) + public static void PlaceFinderPatterns(QRCodeData qrCode, ModuleMatrix blockedModules) { var size = qrCode.ModuleMatrix.Count - 8; @@ -352,7 +383,7 @@ public static void PlaceFinderPatterns(QRCodeData qrCode, BlockedModules blocked } // Add the area covered by the current finder pattern to the list of blocked modules, preventing any data from being placed there. - blockedModules.Add(new Rectangle(locationX, locationY, 7, 7)); + blockedModules.SetModules(new Rectangle(locationX, locationY, 7, 7)); } } @@ -362,7 +393,7 @@ public static void PlaceFinderPatterns(QRCodeData qrCode, BlockedModules blocked /// The QR code data structure where the alignment patterns will be placed. /// A list of points representing the centers of where alignment patterns should be placed. /// A list of rectangles representing areas that must not be overwritten. Updated with the areas occupied by alignment patterns. - public static void PlaceAlignmentPatterns(QRCodeData qrCode, Point[] alignmentPatternLocations, BlockedModules blockedModules) + public static void PlaceAlignmentPatterns(QRCodeData qrCode, Point[] alignmentPatternLocations, ModuleMatrix blockedModules) { // Iterate through each specified location for alignment patterns. foreach (var loc in alignmentPatternLocations) @@ -371,14 +402,14 @@ public static void PlaceAlignmentPatterns(QRCodeData qrCode, Point[] alignmentPa var alignmentPatternRect = new Rectangle(loc.X, loc.Y, 5, 5); // Check if the proposed alignment pattern rectangle intersects with any already blocked rectangles. - if (blockedModules.IsBlocked(alignmentPatternRect)) + if (blockedModules.HasModulesSet(alignmentPatternRect)) { // Skip the current location if it is blocked to prevent overwriting crucial information. continue; } // Add the alignment pattern's area to the list of blocked modules to prevent future overwrites. - blockedModules.Add(alignmentPatternRect); + blockedModules.SetModules(alignmentPatternRect); // Place the alignment pattern by setting modules within the 5x5 area. // The pattern consists of a 3x3 center block with a single module border. @@ -415,7 +446,7 @@ public static void PlaceAlignmentPatterns(QRCodeData qrCode, Point[] alignmentPa /// /// The QR code data structure where the timing patterns will be placed. /// A list of rectangles representing areas that must not be overwritten. Updated with the areas occupied by timing patterns. - public static void PlaceTimingPatterns(QRCodeData qrCode, BlockedModules blockedModules) + public static void PlaceTimingPatterns(QRCodeData qrCode, ModuleMatrix blockedModules) { // Get the size of the QR code matrix excluding padding. var size = qrCode.ModuleMatrix.Count - 8; @@ -433,8 +464,8 @@ public static void PlaceTimingPatterns(QRCodeData qrCode, BlockedModules blocked } // Add the areas occupied by the timing patterns to the list of blocked modules. - blockedModules.Add(new Rectangle(6, 8, 1, size - 16)); // Horizontal timing pattern area - blockedModules.Add(new Rectangle(8, 6, size - 16, 1)); // Vertical timing pattern area + blockedModules.SetModules(new Rectangle(6, 8, 1, size - 16)); // Horizontal timing pattern area + blockedModules.SetModules(new Rectangle(8, 6, size - 16, 1)); // Vertical timing pattern area } else // Micro QR codes { @@ -449,8 +480,8 @@ public static void PlaceTimingPatterns(QRCodeData qrCode, BlockedModules blocked } // Add the areas occupied by the timing patterns to the list of blocked modules. - blockedModules.Add(new Rectangle(0, 8, 1, size - 8)); // Horizontal timing pattern area - blockedModules.Add(new Rectangle(8, 0, size - 8, 1)); // Vertical timing pattern area + blockedModules.SetModules(new Rectangle(0, 8, 1, size - 8)); // Horizontal timing pattern area + blockedModules.SetModules(new Rectangle(8, 0, size - 8, 1)); // Vertical timing pattern area } } } diff --git a/QRCoder/QRCodeGenerator/Rectangle.cs b/QRCoder/QRCodeGenerator/Rectangle.cs index 0200d24d..0e653dba 100644 --- a/QRCoder/QRCodeGenerator/Rectangle.cs +++ b/QRCoder/QRCodeGenerator/Rectangle.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace QRCoder; public partial class QRCodeGenerator @@ -36,6 +38,7 @@ private readonly struct Rectangle /// The height of the rectangle. public Rectangle(int x, int y, int w, int h) { + Debug.Assert(x >= 0 && y >= 0 && w >= 0 && h >= 0); X = x; Y = y; Width = w;