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 40 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1c87314
Split QRCodeGenerator further
Shane32 Apr 6, 2025
e10801d
update
Shane32 Apr 6, 2025
5d16dca
update
Shane32 Apr 6, 2025
f3d17c7
update
Shane32 Apr 6, 2025
5941456
update
Shane32 Apr 6, 2025
6b57a3f
update test
Shane32 Apr 6, 2025
7546d16
update
Shane32 Apr 6, 2025
5a97eb2
update
Shane32 Apr 7, 2025
ae3d1c2
update
Shane32 Apr 12, 2025
ddb443c
Separate GaloisField from CapacityTables
Shane32 Apr 12, 2025
e6fc48f
update
Shane32 Apr 12, 2025
4bd7227
remove using
Shane32 Apr 12, 2025
cca83ad
Progress
Shane32 Apr 13, 2025
e71760b
update format information writing for micro codes
Shane32 Apr 13, 2025
6e9048e
Update
Shane32 Apr 14, 2025
defb582
Update api approvals
Shane32 Apr 14, 2025
cb663d8
update
Shane32 Apr 14, 2025
e759337
progress
Shane32 Apr 14, 2025
696dc86
Fix ECC for M1 and M3 sizes
Shane32 Apr 16, 2025
faac596
Update QRCoder/QRCodeGenerator/GaloisField.cs
Shane32 Apr 16, 2025
f418922
Merge branch 'split' into micro
Shane32 Apr 16, 2025
ec828eb
update
Shane32 Apr 19, 2025
c7a71b1
update
Shane32 Apr 19, 2025
2ce2182
update
Shane32 Jun 2, 2025
a3da262
update
Shane32 Jun 2, 2025
8f351cb
Update QRCoder/QRCodeGenerator.cs
Shane32 Jun 2, 2025
9f59ef1
update
Shane32 Jun 2, 2025
4443236
Update QRCoder/QRCodeGenerator.cs
Shane32 Jun 2, 2025
80c32b7
Update QRCoder/QRCodeGenerator.cs
Shane32 Jun 2, 2025
8358a58
Update QRCoder/QRCodeGenerator.cs
Shane32 Jun 2, 2025
1754386
update
Shane32 Jun 13, 2025
da21485
update
Shane32 Jun 13, 2025
01fb702
update
Shane32 Jun 13, 2025
69f8920
update
Shane32 Jun 13, 2025
f6a3982
update
Shane32 Jun 13, 2025
bbc8b57
Fix CI
Shane32 Jun 17, 2025
b586d66
update
Shane32 Jun 17, 2025
b24ed5a
Merge branch 'fixci2' into micro
Shane32 Jun 17, 2025
7667473
a
Shane32 Jun 17, 2025
c94561b
a
Shane32 Jun 17, 2025
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
247 changes: 229 additions & 18 deletions QRCoder/QRCodeGenerator.cs

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion QRCoder/QRCodeGenerator/AlignmentPatterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ private static class AlignmentPatterns
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);
var localAlignmentPatternTable = new Dictionary<int, AlignmentPattern>(40 + 4);

for (var i = 0; i < (7 * 40); i += 7)
{
Expand Down Expand Up @@ -56,6 +56,14 @@ private static Dictionary<int, AlignmentPattern> CreateAlignmentPatternTable()
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;
}
}
Expand Down
176 changes: 170 additions & 6 deletions QRCoder/QRCodeGenerator/CapacityTables.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ private static class CapacityTables
/// The index in the capacity table corresponds to one less than the version number.
/// </summary>
private static readonly List<VersionInfo> _capacityTable = CreateCapacityTable(_capacityBaseValues);
private static readonly List<VersionInfo> _microCapacityTable = CreateMicroCapacityTable();

/// <summary>
/// A table containing the error correction capacities and data codeword information for different combinations of QR code versions and error correction levels.
Expand All @@ -29,7 +30,7 @@ private static class CapacityTables
/// <summary>
/// Retrieves the error correction information for a specific QR code version and error correction level.
/// </summary>
/// <param name="version">The version of the QR code (1 to 40).</param>
/// <param name="version">The version of the QR code (1 to 40, or -1 to -4 for M1 to M4).</param>
/// <param name="eccLevel">The desired error correction level (L, M, Q, or H). Do not supply <see cref="ECCLevel.Default"/>.</param>
/// <returns>
/// An <see cref="ECCInfo"/> object containing the total number of data codewords, ECC per block,
Expand All @@ -44,25 +45,25 @@ public static ECCInfo GetEccInfo(int version, ECCLevel eccLevel)
/// and encoding mode (Numeric, Alphanumeric, Byte, Kanji), indicating the maximum number of characters
/// that can be stored in a QR code of the specified version under each configuration.
/// </summary>
/// <param name="version">The version of the QR code (1 to 40).</param>
/// <param name="version">The version of the QR code (1 to 40, or -1 to -4 for M1 to M4).</param>
/// <returns>
/// A <see cref="VersionInfo"/> object containing data capacity details for all error correction levels
/// and encoding modes for the specified version.
/// </returns>
public static VersionInfo GetVersionInfo(int version)
=> _capacityTable[version - 1];
=> version < 0 ? _microCapacityTable[-version - 1] : _capacityTable[version - 1];

/// <summary>
/// Retrieves the number of remainder bits required for a specific QR code version.
/// Remainder bits are added to the final bit stream to ensure proper alignment with byte boundaries,
/// as required by the QR code specification.
/// </summary>
/// <param name="version">The version of the QR code (1 to 40).</param>
/// <param name="version">The version of the QR code (1 to 40, or -1 to -4 for M1 to M4).</param>
/// <returns>
/// The number of remainder bits (0 to 7) that must be appended to the encoded bit stream.
/// </returns>
public static int GetRemainderBits(int version)
=> _remainderBits[version - 1];
=> version < 0 ? 0 : _remainderBits[version - 1];

/// <summary>
/// Determines the minimum QR code version required to encode a given amount of data with a specific encoding mode and error correction level.
Expand All @@ -77,6 +78,7 @@ public static int GetRemainderBits(int version)
/// </exception>
public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCLevel eccLevel)
{
// only iterates through non-micro QR codes
// capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found
foreach (var x in _capacityTable)
{
Expand All @@ -99,6 +101,50 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL
throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte);
}


/// <summary>
/// Determines the minimum Micro QR code version required to encode a given amount of data with a specific encoding mode and error correction level.
/// If no suitable version is found, it throws an exception indicating that the data length exceeds the maximum capacity for the given settings.
/// </summary>
/// <param name="length">The length of the data to be encoded.</param>
/// <param name="encMode">The encoding mode (e.g., Numeric, Alphanumeric, Byte).</param>
/// <param name="eccLevel">The error correction level (e.g., Default, Low, Medium, Quartile, High).</param>
/// <returns>The minimum version of the QR code (-1 to -4) that can accommodate the given data and settings.</returns>
/// <exception cref="QRCoder.Exceptions.DataTooLongException">
/// Thrown when the data length exceeds the maximum capacity for the specified encoding mode and error correction level.
/// </exception>
public static int CalculateMinimumMicroVersion(int length, EncodingMode encMode, ECCLevel eccLevel)
{
// only iterates through non-micro QR codes
// capacity table is already sorted by version number ascending, so the smallest version that can hold the data is the first one found
foreach (var x in _microCapacityTable)
{
// find the requested ECC level and encoding mode in the capacity table
foreach (var y in x.Details)
{
// Use ECC level L for Micro QR Code versions 2, 3 and 4 when Default is specified
if (y.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && y.ErrorCorrectionLevel == ECCLevel.L))
{
// Not all versions support all encoding modes, so check if the encoding mode is supported
if (y.CapacityDict.TryGetValue(encMode, out int maxLength) && maxLength >= length)
{
// if the capacity of the current version is enough, return the version number
return x.Version;
}
}
}
}

// if no version was found, throw an exception
var maxSizeByte = _microCapacityTable
.SelectMany(x => x.Details)
.Where(y => (y.ErrorCorrectionLevel == eccLevel || (eccLevel == ECCLevel.Default && y.ErrorCorrectionLevel == ECCLevel.L))
&& y.CapacityDict.ContainsKey(encMode))
.Max(y => y.CapacityDict[encMode]);

throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte);
}

/// <summary>
/// Generates a table containing the error correction capacities and data codeword information for different QR code versions and error correction levels.
/// This table is essential for determining how much data can be encoded in a QR code of a specific version and ECC level,
Expand All @@ -107,7 +153,7 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL
/// <returns>A list of ECCInfo structures, each representing the ECC data and capacities for different combinations of QR code versions and ECC levels.</returns>
private static List<ECCInfo> CreateCapacityECCTable(int[] capacityECCBaseValues)
{
var localCapacityECCTable = new List<ECCInfo>(160);
var localCapacityECCTable = new List<ECCInfo>(160 + 8);
for (var i = 0; i < (4 * 6 * 40); i += (4 * 6))
{
localCapacityECCTable.AddRange(new[]
Expand Down Expand Up @@ -153,6 +199,31 @@ private static List<ECCInfo> CreateCapacityECCTable(int[] capacityECCBaseValues)
)
});
}

localCapacityECCTable.AddRange(new ECCInfo[]
{
// Micro QR Code Version M1 - only supports ECCLevel.Default (none)
new ECCInfo(
version: -1,
errorCorrectionLevel: ECCLevel.Default,
totalDataCodewords: 3,
totalDataBits: 20,
eccPerBlock: 2),

// Micro QR Code Version M2
new ECCInfo(-2, ECCLevel.L, 5, 40, 5),
new ECCInfo(-2, ECCLevel.M, 4, 32, 6),

// Micro QR Code Version M3
new ECCInfo(-3, ECCLevel.L, 11, 84, 6),
new ECCInfo(-3, ECCLevel.M, 9, 68, 8),

// Micro QR Code Version M4
new ECCInfo(-4, ECCLevel.L, 16, 128, 8),
new ECCInfo(-4, ECCLevel.M, 14, 112, 10),
new ECCInfo(-4, ECCLevel.Q, 10, 80, 14),
});

return localCapacityECCTable;
}

Expand Down Expand Up @@ -212,5 +283,98 @@ private static List<VersionInfo> CreateCapacityTable(int[] capacityBaseValues)
}
return localCapacityTable;
}

/// <inheritdoc cref="CreateCapacityTable(int[])"/>
private static List<VersionInfo> CreateMicroCapacityTable()
{
var tbl = new List<VersionInfo>(4);

var m1details = new List<VersionInfoDetails>(1)
{
new VersionInfoDetails(
ECCLevel.Default, // none
new Dictionary<EncodingMode, int>(1) {
{ EncodingMode.Numeric, 5 },
}
)
};
tbl.Add(new VersionInfo(-1, m1details));

var m2details = new List<VersionInfoDetails>(2)
{
new VersionInfoDetails(
ECCLevel.L,
new Dictionary<EncodingMode, int>(2) {
{ EncodingMode.Numeric, 10 },
{ EncodingMode.Alphanumeric, 6 },
}
),
new VersionInfoDetails(
ECCLevel.M,
new Dictionary<EncodingMode, int>(2) {
{ EncodingMode.Numeric, 8 },
{ EncodingMode.Alphanumeric, 5 },
}
),
};
tbl.Add(new VersionInfo(-2, m2details));

var m3details = new List<VersionInfoDetails>(2)
{
new VersionInfoDetails(
ECCLevel.L,
new Dictionary<EncodingMode, int>(4) {
{ EncodingMode.Numeric, 23 },
{ EncodingMode.Alphanumeric, 14 },
{ EncodingMode.Byte, 9 },
{ EncodingMode.Kanji, 6 },
}
),
new VersionInfoDetails(
ECCLevel.M,
new Dictionary<EncodingMode, int>(4) {
{ EncodingMode.Numeric, 18 },
{ EncodingMode.Alphanumeric, 11 },
{ EncodingMode.Byte, 7 },
{ EncodingMode.Kanji, 4 },
}
),
};
tbl.Add(new VersionInfo(-3, m3details));

var m4details = new List<VersionInfoDetails>(3)
{
new VersionInfoDetails(
ECCLevel.L,
new Dictionary<EncodingMode, int>(4) {
{ EncodingMode.Numeric, 35 },
{ EncodingMode.Alphanumeric, 21 },
{ EncodingMode.Byte, 15 },
{ EncodingMode.Kanji, 9 },
}
),
new VersionInfoDetails(
ECCLevel.M,
new Dictionary<EncodingMode, int>(4) {
{ EncodingMode.Numeric, 30 },
{ EncodingMode.Alphanumeric, 18 },
{ EncodingMode.Byte, 13 },
{ EncodingMode.Kanji, 8 },
}
),
new VersionInfoDetails(
ECCLevel.Q,
new Dictionary<EncodingMode, int>(4) {
{ EncodingMode.Numeric, 21 },
{ EncodingMode.Alphanumeric, 13 },
{ EncodingMode.Byte, 9 },
{ EncodingMode.Kanji, 5 },
}
),
};
tbl.Add(new VersionInfo(-4, m4details));

return tbl;
}
}
}
27 changes: 27 additions & 0 deletions QRCoder/QRCodeGenerator/ECCInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,35 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword
Version = version;
ErrorCorrectionLevel = errorCorrectionLevel;
TotalDataCodewords = totalDataCodewords;
TotalDataBits = totalDataCodewords * 8;
ECCPerBlock = eccPerBlock;
BlocksInGroup1 = blocksInGroup1;
CodewordsInGroup1 = codewordsInGroup1;
BlocksInGroup2 = blocksInGroup2;
CodewordsInGroup2 = codewordsInGroup2;
}

/// <summary>
/// Initializes a new instance of the ECCInfo struct with specified properties for Micro QR codes.
/// </summary>
/// <param name="version">The version number of the QR code.</param>
/// <param name="errorCorrectionLevel">The error correction level used in the QR code.</param>
/// <param name="totalDataCodewords">The total number of data codewords for this version and error correction level.</param>
/// <param name="totalDataBits">The total number of data bits for this version and error correction level.</param>
/// <param name="eccPerBlock">The number of error correction codewords per block.</param>
public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int totalDataBits, int eccPerBlock)
{
Version = version;
ErrorCorrectionLevel = errorCorrectionLevel;
TotalDataCodewords = totalDataCodewords;
TotalDataBits = totalDataBits;
ECCPerBlock = eccPerBlock;
BlocksInGroup1 = 1;
CodewordsInGroup1 = totalDataCodewords;
BlocksInGroup2 = 0;
CodewordsInGroup2 = 0;
}

/// <summary>
/// Gets the version number of the QR code.
/// </summary>
Expand All @@ -46,6 +68,11 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword
/// </summary>
public int TotalDataCodewords { get; }

/// <summary>
/// Gets the total number of data codewords for this version and error correction level.
/// </summary>
public int TotalDataBits { get; }

/// <summary>
/// Gets the number of error correction codewords per block.
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions QRCoder/QRCodeGenerator/ModulePlacer.MaskPattern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ public static bool Pattern7(int x, int y)
public static bool Pattern8(int x, int y)
=> (((x + y) % 2) + ((x * y) % 3)) % 2 == 0;

/// <summary>
/// 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.
/// </summary>
/// <param name="qrCode">The QR code data structure to be evaluated.</param>
/// <returns>The total penalty score of the QR code.</returns>
public static int ScoreMicro(QRCodeData qrCode)
{
var size = qrCode.ModuleMatrix.Count;
int sum1 = 0;
int sum2 = 0;
for (var 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
}

/// <summary>
/// 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.
Expand Down
Loading
Loading