Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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.

73 changes: 41 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,64 @@ 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>
/// 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 + 4];

/// <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 + 4] = empty;
localAlignmentPatternTable[-3 + 4] = empty;
localAlignmentPatternTable[-2 + 4] = empty;
localAlignmentPatternTable[-1 + 4] = empty;
Comment thread
Shane32 marked this conversation as resolved.
Outdated

// A Version 1 QR code does not have alignment patterns.
localAlignmentPatternTable[1 + 4] = empty;

#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 i = 0; i < (7 * 40); i += 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 + 4] = 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