Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions QRCoder/QRCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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);
Expand Down
22 changes: 0 additions & 22 deletions QRCoder/QRCodeGenerator/AlignmentPattern.cs

This file was deleted.

78 changes: 46 additions & 32 deletions QRCoder/QRCodeGenerator/AlignmentPatterns.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Diagnostics;

namespace QRCoder;

public partial class QRCodeGenerator
Expand All @@ -10,57 +12,69 @@ private static class AlignmentPatterns
/// <summary>
/// A lookup table mapping QR code versions to their corresponding alignment patterns.
/// </summary>
private static readonly Dictionary<int, AlignmentPattern> _alignmentPatternTable = CreateAlignmentPatternTable();
/// <remarks>
/// Versions range from -4 up to and including -1 and 1 up to and including 40.
/// </remarks>
private static readonly Point[][] _alignmentPatternTable = CreateAlignmentPatternTable();

/// <summary>
/// Offset used to map -4 .. 40 to 0 .. 44
/// </summary>
private const int INDEX_OFFSET = 4;

/// <summary>
/// Retrieves the alignment pattern for a specific QR code version.
/// </summary>
public static AlignmentPattern FromVersion(int version) => _alignmentPatternTable[version];
public static Point[] FromVersion(int version) => _alignmentPatternTable[version + INDEX_OFFSET];

/// <summary>
/// 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.
/// </summary>
/// <returns>A dictionary where keys are QR code version numbers and values are AlignmentPattern structures detailing the positions of alignment patterns for each version.</returns>
private static Dictionary<int, AlignmentPattern> CreateAlignmentPatternTable()
/// <returns>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.</returns>
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<int, AlignmentPattern>(40 + 4);
var localAlignmentPatternTable = new Point[4 + 1 + 40][];

// Micro QR codes do not have alignment patterns.
Point[] 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 + INDEX_OFFSET] = empty;

for (var i = 0; i < (7 * 40); i += 7)
#if HAS_SPAN
ReadOnlySpan<byte> 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<Point>(7 * 7);

for (var version = 2; version <= 40; version++)
{
var points = new List<Point>(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<Point>();
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 + INDEX_OFFSET] = points.ToArray();

points.Clear();
}

return localAlignmentPatternTable;
}
Expand Down
36 changes: 25 additions & 11 deletions QRCoder/QRCodeGenerator/ModulePlacer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ public static void PlaceFinderPatterns(QRCodeData qrCode, BlockedModules blocked
/// <param name="qrCode">The QR code data structure where the alignment patterns will be placed.</param>
/// <param name="alignmentPatternLocations">A list of points representing the centers of where alignment patterns should be placed.</param>
/// <param name="blockedModules">A list of rectangles representing areas that must not be overwritten. Updated with the areas occupied by alignment patterns.</param>
public static void PlaceAlignmentPatterns(QRCodeData qrCode, List<Point> 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)
Expand All @@ -377,22 +377,36 @@ public static void PlaceAlignmentPatterns(QRCodeData qrCode, List<Point> 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));
}
}

Expand Down
35 changes: 5 additions & 30 deletions QRCoder/QRCodeGenerator/Point.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,31 @@
using System.Reflection;

namespace QRCoder;

public partial class QRCodeGenerator
{
/// <summary>
/// Represents a 2D point with integer coordinates.
/// Represents a 2D point with byte coordinates.
/// </summary>
private readonly struct Point : IEquatable<Point>
private readonly struct Point
{
/// <summary>
/// Gets the X-coordinate of the point.
/// </summary>
public int X { get; }
public byte X { get; }

/// <summary>
/// Gets the Y-coordinate of the point.
/// </summary>
public int Y { get; }
public byte Y { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Point"/> struct with specified X and Y coordinates.
/// </summary>
/// <param name="x">The X-coordinate of the point.</param>
/// <param name="y">The Y-coordinate of the point.</param>
public Point(int x, int y)
public Point(byte x, byte y)
{
X = x;
Y = y;
}

/// <summary>
/// Determines whether the specified <see cref="Point"/> is equal to the current <see cref="Point"/>.
/// </summary>
/// <param name="other">The <see cref="Point"/> to compare with the current <see cref="Point"/>.</param>
/// <returns>True if the specified <see cref="Point"/> has the same X and Y coordinates as the current <see cref="Point"/>; otherwise, false.</returns>
/// <remarks>
/// If this method which implements <see cref="IEquatable{T}.Equals(T)"/> is not implemented, comparisons used by methods such as <see cref="List{T}.Contains(T)"/>
/// fall back to reflection, which causes heap allocations internally during the calls to <see cref="FieldInfo.GetValue(object)"/>.
/// </remarks>
public bool Equals(Point other)
=> X == other.X && Y == other.Y;

/// <inheritdoc/>
public override bool Equals(object? obj) => obj is Point point && Equals(point);

/// <inheritdoc/>
public override int GetHashCode()
#if NET5_0_OR_GREATER
=> HashCode.Combine(X, Y);
#else
=> X ^ (int)(((uint)Y << 16) | ((uint)Y >> 16));
#endif
}
}
Loading