Skip to content

Add Micro QR code support #592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
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
6 changes: 4 additions & 2 deletions QRCoder/QRCodeData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,12 @@ public void SaveRawData(string filePath, Compression compressMode)
/// <summary>
/// Gets the number of modules per side from the specified version.
/// </summary>
/// <param name="version">The version of the QR code.</param>
/// <param name="version">The version of the QR code (1 to 40, or -1 to -4 for Micro QR codes).</param>
/// <returns>Returns the number of modules per side.</returns>
private static int ModulesPerSideFromVersion(int version)
=> 21 + (version - 1) * 4;
=> version > 0
? 21 + (version - 1) * 4
: 11 + (-version - 1) * 2;

/// <summary>
/// Releases all resources used by the <see cref="QRCodeData"/>.
Expand Down
556 changes: 242 additions & 314 deletions QRCoder/QRCodeGenerator.cs

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions QRCoder/QRCodeGenerator/AlignmentPatterns.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Collections.Generic;

namespace QRCoder;

public partial class QRCodeGenerator
{
/// <summary>
/// This class contains the alignment patterns used in QR codes.
/// </summary>
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();

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

/// <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()
{
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 + 8);

for (var i = 0; i < (7 * 40); i += 7)
{
var points = new List<Point>(50);
for (var x = 0; x < 7; x++)
{
if (alignmentPatternBaseValues[i + x] != 0)
{
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);
}
}
}
}

var version = (i + 7) / 7;
localAlignmentPatternTable.Add(version, new AlignmentPattern()
{
Version = version,
PatternPositions = points
});
}

// 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 });

return localAlignmentPatternTable;
}
}
}
81 changes: 81 additions & 0 deletions QRCoder/QRCodeGenerator/AlphanumericEncoder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace QRCoder;

public partial class QRCodeGenerator
{
/// <summary>
/// Encodes alphanumeric characters (<c>0–9</c>, <c>A–Z</c> (uppercase), space, <c>$</c>, <c>%</c>, <c>*</c>, <c>+</c>, <c>-</c>, period, <c>/</c>, colon) into a binary format suitable for QR codes.
/// </summary>
private static class AlphanumericEncoder
{
private static readonly char[] _alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' };

/// <summary>
/// A dictionary mapping alphanumeric characters to their respective positions used in QR code encoding.
/// This includes digits 0-9, uppercase letters A-Z, and some special characters.
/// </summary>
private static readonly Dictionary<char, int> _alphanumEncDict = CreateAlphanumEncDict(_alphanumEncTable);

/// <summary>
/// Creates a dictionary mapping alphanumeric characters to their respective positions used in QR code encoding.
/// This includes digits 0-9, uppercase letters A-Z, and some special characters.
/// </summary>
/// <returns>A dictionary mapping each supported alphanumeric character to its corresponding value.</returns>
private static Dictionary<char, int> CreateAlphanumEncDict(char[] alphanumEncTable)
{
var localAlphanumEncDict = new Dictionary<char, int>(45);
// Add 0-9
for (char c = '0'; c <= '9'; c++)
localAlphanumEncDict.Add(c, c - '0');
// Add uppercase alphabetic characters.
for (char c = 'A'; c <= 'Z'; c++)
localAlphanumEncDict.Add(c, localAlphanumEncDict.Count);
// Add special characters from a predefined table.
for (int i = 0; i < _alphanumEncTable.Length; i++)
localAlphanumEncDict.Add(alphanumEncTable[i], localAlphanumEncDict.Count);
return localAlphanumEncDict;
}

/// <summary>
/// Checks if a character is present in the alphanumeric encoding table.
/// </summary>
public static bool CanEncode(char c) => IsInRange(c, 'A', 'Z') || Array.IndexOf(_alphanumEncTable, c) >= 0;

/// <summary>
/// Converts alphanumeric plain text into a binary format optimized for QR codes.
/// Alphanumeric encoding packs characters into 11-bit groups for each pair of characters,
/// and 6 bits for a single remaining character if the total count is odd.
/// </summary>
/// <param name="plainText">The alphanumeric text to be encoded, which should only contain characters valid in QR alphanumeric mode.</param>
/// <returns>A BitArray representing the binary data of the encoded alphanumeric text.</returns>
public static BitArray GetBitArray(string plainText)
{
// Calculate the length of the BitArray needed based on the number of character pairs.
var codeText = new BitArray((plainText.Length / 2) * 11 + (plainText.Length & 1) * 6);
var codeIndex = 0;
var index = 0;
var count = plainText.Length;

// Process each pair of characters.
while (count >= 2)
{
// Convert each pair of characters to a number by looking them up in the alphanumeric dictionary and calculating.
var dec = _alphanumEncDict[plainText[index++]] * 45 + _alphanumEncDict[plainText[index++]];
// Convert the number to binary and store it in the BitArray.
codeIndex = DecToBin(dec, 11, codeText, codeIndex);
count -= 2;
}

// Handle the last character if the length is odd.
if (count > 0)
{
DecToBin(_alphanumEncDict[plainText[index]], 6, codeText, codeIndex);
}

return codeText;
}
}
}
Loading
Loading