From a61ad31b7d91ae2f1cc0765567c0af9557f43b32 Mon Sep 17 00:00:00 2001 From: Kris Vandermotten Date: Sat, 1 Aug 2026 11:32:21 +0200 Subject: [PATCH 1/2] Optimize AlignmentPatterns --- QRCoder/QRCodeGenerator.cs | 4 +- QRCoder/QRCodeGenerator/AlignmentPattern.cs | 22 ------ QRCoder/QRCodeGenerator/AlignmentPatterns.cs | 73 +++++++++++--------- QRCoder/QRCodeGenerator/ModulePlacer.cs | 36 +++++++--- QRCoder/QRCodeGenerator/Point.cs | 35 ++-------- 5 files changed, 72 insertions(+), 98 deletions(-) delete mode 100644 QRCoder/QRCodeGenerator/AlignmentPattern.cs diff --git a/QRCoder/QRCodeGenerator.cs b/QRCoder/QRCodeGenerator.cs index a0c02dc7..ff1e215c 100644 --- a/QRCoder/QRCodeGenerator.cs +++ b/QRCoder/QRCodeGenerator.cs @@ -1,10 +1,8 @@ #if HAS_SPAN using System.Buffers; #endif -using System; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Text; namespace QRCoder; @@ -458,7 +456,7 @@ QRCodeData PlaceModules() { ModulePlacer.PlaceFinderPatterns(qr, blockedModules); ModulePlacer.ReserveSeperatorAreas(version, size, blockedModules); - ModulePlacer.PlaceAlignmentPatterns(qr, AlignmentPatterns.FromVersion(version).PatternPositions, blockedModules); + ModulePlacer.PlaceAlignmentPatterns(qr, AlignmentPatterns.FromVersion(version), blockedModules); ModulePlacer.PlaceTimingPatterns(qr, blockedModules); ModulePlacer.PlaceDarkModule(qr, version, blockedModules); ModulePlacer.ReserveVersionAreas(size, version, blockedModules); diff --git a/QRCoder/QRCodeGenerator/AlignmentPattern.cs b/QRCoder/QRCodeGenerator/AlignmentPattern.cs deleted file mode 100644 index cc795367..00000000 --- a/QRCoder/QRCodeGenerator/AlignmentPattern.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace QRCoder; - -public partial class QRCodeGenerator -{ - /// - /// Represents the alignment pattern used in QR codes, which helps ensure the code remains readable even if it is somewhat distorted. - /// Each QR code version has its own specific alignment pattern locations which this struct encapsulates. - /// - private struct AlignmentPattern - { - /// - /// The version of the QR code. Higher versions have more complex and numerous alignment patterns. - /// - public int Version; - - /// - /// A list of points where alignment patterns are located within the QR code matrix. - /// Each point represents the center of an alignment pattern. - /// - public List PatternPositions; - } -} diff --git a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs index 8b09a1a5..5b5d0e25 100644 --- a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs +++ b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; + namespace QRCoder; public partial class QRCodeGenerator @@ -10,57 +12,64 @@ private static class AlignmentPatterns /// /// A lookup table mapping QR code versions to their corresponding alignment patterns. /// - private static readonly Dictionary _alignmentPatternTable = CreateAlignmentPatternTable(); + /// + /// Versions range from -4 up to and including -1 and 1 up to and including 40. + /// + private static readonly Point[][] _alignmentPatternTable = CreateAlignmentPatternTable(); /// /// Retrieves the alignment pattern for a specific QR code version. /// - public static AlignmentPattern FromVersion(int version) => _alignmentPatternTable[version]; + public static Point[] FromVersion(int version) => _alignmentPatternTable[version + 4]; /// /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. /// Alignment patterns are used in QR codes to help scanners accurately read the code at high speeds and when partially obscured. /// This table provides the necessary patterns based on the QR code version which dictates the size and complexity of the QR code. /// - /// A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version. - private static Dictionary CreateAlignmentPatternTable() + /// An array where indices are QR code version numbers offset by 4 and values are Point arrays containing the positions of alignment patterns for each version. + private static Point[][] CreateAlignmentPatternTable() { - var alignmentPatternBaseValues = new int[] { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; - var localAlignmentPatternTable = new Dictionary(40 + 4); + var localAlignmentPatternTable = new Point[4 + 1 + 40][]; + + // Micro QR codes do not have alignment patterns. + Point[] empty = []; + + localAlignmentPatternTable[-4 + 4] = empty; + localAlignmentPatternTable[-3 + 4] = empty; + localAlignmentPatternTable[-2 + 4] = empty; + localAlignmentPatternTable[-1 + 4] = empty; + + // A Version 1 QR code does not have alignment patterns. + localAlignmentPatternTable[1 + 4] = empty; + +#if HAS_SPAN + ReadOnlySpan alignmentPatternBaseValues = +#else + byte[] alignmentPatternBaseValues = +#endif + [4, 16, 0, 0, 0, 0, 0, 4, 20, 0, 0, 0, 0, 0, 4, 24, 0, 0, 0, 0, 0, 4, 28, 0, 0, 0, 0, 0, 4, 32, 0, 0, 0, 0, 0, 4, 20, 36, 0, 0, 0, 0, 4, 22, 40, 0, 0, 0, 0, 4, 24, 44, 0, 0, 0, 0, 4, 26, 48, 0, 0, 0, 0, 4, 28, 52, 0, 0, 0, 0, 4, 30, 56, 0, 0, 0, 0, 4, 32, 60, 0, 0, 0, 0, 4, 24, 44, 64, 0, 0, 0, 4, 24, 46, 68, 0, 0, 0, 4, 24, 48, 72, 0, 0, 0, 4, 28, 52, 76, 0, 0, 0, 4, 28, 54, 80, 0, 0, 0, 4, 28, 56, 84, 0, 0, 0, 4, 32, 60, 88, 0, 0, 0, 4, 26, 48, 70, 92, 0, 0, 4, 24, 48, 72, 96, 0, 0, 4, 28, 52, 76, 100, 0, 0, 4, 26, 52, 78, 104, 0, 0, 4, 30, 56, 82, 108, 0, 0, 4, 28, 56, 84, 112, 0, 0, 4, 32, 60, 88, 116, 0, 0, 4, 24, 48, 72, 96, 120, 0, 4, 28, 52, 76, 100, 124, 0, 4, 24, 50, 76, 102, 128, 0, 4, 28, 54, 80, 106, 132, 0, 4, 32, 58, 84, 110, 136, 0, 4, 28, 56, 84, 112, 140, 0, 4, 32, 60, 88, 116, 144, 0, 4, 28, 52, 76, 100, 124, 148, 4, 22, 48, 74, 100, 126, 152, 4, 26, 52, 78, 104, 130, 156, 4, 30, 56, 82, 108, 134, 160, 4, 24, 52, 80, 108, 136, 164, 4, 28, 56, 84, 112, 140, 168]; + + var points = new List(7 * 7); - for (var i = 0; i < (7 * 40); i += 7) + for (var version = 2; version <= 40; version++) { - var points = new List(50); - for (var x = 0; x < 7; x++) + var indexBase = (version - 2) * 7; + + for (var x = 0; x < 7 && alignmentPatternBaseValues[indexBase + x] != 0; x++) { - if (alignmentPatternBaseValues[i + x] != 0) + for (var y = 0; y < 7 && alignmentPatternBaseValues[indexBase + y] != 0; y++) { - for (var y = 0; y < 7; y++) - { - if (alignmentPatternBaseValues[i + y] != 0) - { - var p = new Point(alignmentPatternBaseValues[i + x] - 2, alignmentPatternBaseValues[i + y] - 2); - if (!points.Contains(p)) - points.Add(p); - } - } + points.Add(new Point(alignmentPatternBaseValues[indexBase + x], alignmentPatternBaseValues[indexBase + y])); } } - var version = (i + 7) / 7; - localAlignmentPatternTable.Add(version, new AlignmentPattern() - { - Version = version, - PatternPositions = points - }); - } + Debug.Assert(points.Count <= 7 * 7); - // Micro QR codes do not have alignment patterns. - var emptyPointList = new List(); - localAlignmentPatternTable.Add(-1, new AlignmentPattern { Version = -1, PatternPositions = emptyPointList }); - localAlignmentPatternTable.Add(-2, new AlignmentPattern { Version = -2, PatternPositions = emptyPointList }); - localAlignmentPatternTable.Add(-3, new AlignmentPattern { Version = -3, PatternPositions = emptyPointList }); - localAlignmentPatternTable.Add(-4, new AlignmentPattern { Version = -4, PatternPositions = emptyPointList }); + localAlignmentPatternTable[version + 4] = points.ToArray(); + + points.Clear(); + } return localAlignmentPatternTable; } diff --git a/QRCoder/QRCodeGenerator/ModulePlacer.cs b/QRCoder/QRCodeGenerator/ModulePlacer.cs index 989c7049..771cd8ae 100644 --- a/QRCoder/QRCodeGenerator/ModulePlacer.cs +++ b/QRCoder/QRCodeGenerator/ModulePlacer.cs @@ -362,7 +362,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, List alignmentPatternLocations, BlockedModules blockedModules) + public static void PlaceAlignmentPatterns(QRCodeData qrCode, Point[] alignmentPatternLocations, BlockedModules blockedModules) { // Iterate through each specified location for alignment patterns. foreach (var loc in alignmentPatternLocations) @@ -377,22 +377,36 @@ public static void PlaceAlignmentPatterns(QRCodeData qrCode, List alignme continue; } + // Add the alignment pattern's area to the list of blocked modules to prevent future overwrites. + blockedModules.Add(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. - for (var x = 0; x < 5; x++) + // Create the pattern: a 3x3 block surrounded by a border, with the very center module set. + for (var y = 0; y < 5; y++) { - for (var y = 0; y < 5; y++) + var moduleRow = qrCode.ModuleMatrix[loc.Y + 4 + y]; + + switch (y) { - // Create the pattern: a 3x3 block surrounded by a border, with the very center module set. - if (y == 0 || y == 4 || x == 0 || x == 4 || (x == 2 && y == 2)) - { - qrCode.ModuleMatrix[loc.Y + y + 4][loc.X + x + 4] = true; - } + case 0 or 4: + moduleRow[loc.X + 4 + 0] = true; + moduleRow[loc.X + 4 + 1] = true; + moduleRow[loc.X + 4 + 2] = true; + moduleRow[loc.X + 4 + 3] = true; + moduleRow[loc.X + 4 + 4] = true; + break; + case 1 or 3: + moduleRow[loc.X + 4 + 0] = true; + moduleRow[loc.X + 4 + 4] = true; + break; + case 2: + moduleRow[loc.X + 4 + 0] = true; + moduleRow[loc.X + 4 + 2] = true; + moduleRow[loc.X + 4 + 4] = true; + break; } } - - // Add the alignment pattern's area to the list of blocked modules to prevent future overwrites. - blockedModules.Add(new Rectangle(loc.X, loc.Y, 5, 5)); } } diff --git a/QRCoder/QRCodeGenerator/Point.cs b/QRCoder/QRCodeGenerator/Point.cs index 6ec9d532..cdd6b4ad 100644 --- a/QRCoder/QRCodeGenerator/Point.cs +++ b/QRCoder/QRCodeGenerator/Point.cs @@ -1,56 +1,31 @@ -using System.Reflection; - namespace QRCoder; public partial class QRCodeGenerator { /// - /// Represents a 2D point with integer coordinates. + /// Represents a 2D point with byte coordinates. /// - private readonly struct Point : IEquatable + private readonly struct Point { /// /// Gets the X-coordinate of the point. /// - public int X { get; } + public byte X { get; } /// /// Gets the Y-coordinate of the point. /// - public int Y { get; } + public byte Y { get; } /// /// Initializes a new instance of the struct with specified X and Y coordinates. /// /// The X-coordinate of the point. /// The Y-coordinate of the point. - public Point(int x, int y) + public Point(byte x, byte y) { X = x; Y = y; } - - /// - /// Determines whether the specified is equal to the current . - /// - /// The to compare with the current . - /// True if the specified has the same X and Y coordinates as the current ; otherwise, false. - /// - /// If this method which implements is not implemented, comparisons used by methods such as - /// fall back to reflection, which causes heap allocations internally during the calls to . - /// - public bool Equals(Point other) - => X == other.X && Y == other.Y; - - /// - public override bool Equals(object? obj) => obj is Point point && Equals(point); - - /// - public override int GetHashCode() -#if NET5_0_OR_GREATER - => HashCode.Combine(X, Y); -#else - => X ^ (int)(((uint)Y << 16) | ((uint)Y >> 16)); -#endif } } From de2309f27ad68f75bc2421c642b3c7232462d57b Mon Sep 17 00:00:00 2001 From: Kris Vandermotten Date: Sat, 8 Aug 2026 19:17:29 +0200 Subject: [PATCH 2/2] Use INDEX_OFFSET constant --- QRCoder/QRCodeGenerator/AlignmentPatterns.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs index 5b5d0e25..81a2f02d 100644 --- a/QRCoder/QRCodeGenerator/AlignmentPatterns.cs +++ b/QRCoder/QRCodeGenerator/AlignmentPatterns.cs @@ -17,10 +17,15 @@ private static class AlignmentPatterns /// private static readonly Point[][] _alignmentPatternTable = CreateAlignmentPatternTable(); + /// + /// Offset used to map -4 .. 40 to 0 .. 44 + /// + private const int INDEX_OFFSET = 4; + /// /// Retrieves the alignment pattern for a specific QR code version. /// - public static Point[] FromVersion(int version) => _alignmentPatternTable[version + 4]; + public static Point[] FromVersion(int version) => _alignmentPatternTable[version + INDEX_OFFSET]; /// /// Creates a lookup table mapping QR code versions to their corresponding alignment patterns. @@ -35,13 +40,13 @@ private static Point[][] CreateAlignmentPatternTable() // Micro QR codes do not have alignment patterns. Point[] empty = []; - localAlignmentPatternTable[-4 + 4] = empty; - localAlignmentPatternTable[-3 + 4] = empty; - localAlignmentPatternTable[-2 + 4] = empty; - localAlignmentPatternTable[-1 + 4] = empty; + localAlignmentPatternTable[-4 + INDEX_OFFSET] = empty; + localAlignmentPatternTable[-3 + INDEX_OFFSET] = empty; + localAlignmentPatternTable[-2 + INDEX_OFFSET] = empty; + localAlignmentPatternTable[-1 + INDEX_OFFSET] = empty; // A Version 1 QR code does not have alignment patterns. - localAlignmentPatternTable[1 + 4] = empty; + localAlignmentPatternTable[1 + INDEX_OFFSET] = empty; #if HAS_SPAN ReadOnlySpan alignmentPatternBaseValues = @@ -66,7 +71,7 @@ private static Point[][] CreateAlignmentPatternTable() Debug.Assert(points.Count <= 7 * 7); - localAlignmentPatternTable[version + 4] = points.ToArray(); + localAlignmentPatternTable[version + INDEX_OFFSET] = points.ToArray(); points.Clear(); }