Skip to content

Commit

Permalink
Merge branch 'master' into add_more_tests
Browse files Browse the repository at this point in the history
  • Loading branch information
codebude authored May 19, 2024
2 parents c732d19 + 02baa95 commit b111fd0
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 88 deletions.
2 changes: 1 addition & 1 deletion QRCoder/PayloadGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static class PayloadGenerator
public abstract class Payload
{
public virtual int Version { get { return -1; } }
public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } }
public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.Default; } }
public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } }
public abstract override string ToString();
}
Expand Down
14 changes: 10 additions & 4 deletions QRCoder/QRCodeGenerator.ECCLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,35 @@ public partial class QRCodeGenerator
/// </summary>
public enum ECCLevel
{
/// <summary>
/// Default error correction level, which will select Level M (Medium) unless otherwise specified by the payload.
/// Level M allows approximately 15% of data to be recovered, offering a balance between data capacity and error recovery.
/// </summary>
Default = -1,

/// <summary>
/// Level L: Low error correction (approximately 7% of data can be recovered).
/// This level allows the highest data density.
/// </summary>
L,
L = 0,

/// <summary>
/// Level M: Medium error correction (approximately 15% of data can be recovered).
/// Offers a balance between data capacity and error recovery.
/// </summary>
M,
M = 1,

/// <summary>
/// Level Q: Quartile error correction (approximately 25% of data can be recovered).
/// More robust error correction at the cost of reduced data capacity.
/// </summary>
Q,
Q = 2,

/// <summary>
/// Level H: High error correction (approximately 30% of data can be recovered).
/// Provides the highest level of error recovery, ideal for environments with high risk of data loss.
/// </summary>
H
H = 3
}
}
}
230 changes: 147 additions & 83 deletions QRCoder/QRCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload)
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLevel eccLevel)
{
if (eccLevel == ECCLevel.Default)
eccLevel = payload.EccLevel;
else if (payload.EccLevel != ECCLevel.Default && eccLevel != payload.EccLevel)
throw new ArgumentOutOfRangeException(nameof(eccLevel), $"The provided payload requires a ECC level of {eccLevel}.");
return GenerateQrCode(payload.ToString(), eccLevel, false, false, payload.EciMode, payload.Version);
}

Expand All @@ -121,6 +125,7 @@ public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLev
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1)
{
eccLevel = ValidateECCLevel(eccLevel);
EncodingMode encoding = GetEncodingFromPlaintext(plainText, forceUtf8);
var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8);
var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8);
Expand Down Expand Up @@ -165,7 +170,6 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
return GenerateQrCode(completeBitArray, eccLevel, version);
}


/// <summary>
/// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation.
/// </summary>
Expand All @@ -175,6 +179,7 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
/// <returns>Returns the raw QR code data which can be used for rendering.</returns>
public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
{
eccLevel = ValidateECCLevel(eccLevel);
int version = GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel);

int countIndicatorLen = GetCountIndicatorLength(version, EncodingMode.Byte);
Expand All @@ -187,6 +192,27 @@ public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
return GenerateQrCode(bitArray, eccLevel, version);
}

/// <summary>
/// Validates the specified error correction level.
/// Returns the provided level if it is valid, or the level M if the provided level is Default.
/// Throws an exception if an invalid level is provided.
/// </summary>
private static ECCLevel ValidateECCLevel(ECCLevel eccLevel)
{
switch (eccLevel)
{
case ECCLevel.L:
case ECCLevel.M:
case ECCLevel.Q:
case ECCLevel.H:
return eccLevel;
case ECCLevel.Default:
return ECCLevel.M;
default:
throw new ArgumentOutOfRangeException(nameof(eccLevel), eccLevel, "Invalid error correction level.");
}
}

private static readonly BitArray _repeatingPattern = new BitArray(
new[] { true, true, true, false, true, true, false, false, false, false, false, true, false, false, false, true });

Expand All @@ -200,109 +226,147 @@ public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel)
/// <returns>A QRCodeData structure containing the full QR code matrix, which can be used for rendering or analysis.</returns>
private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, int version)
{
//Fill up data code word
var eccInfo = capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel);
var dataLength = eccInfo.TotalDataCodewords * 8;
var lengthDiff = dataLength - bitArray.Length;
if (lengthDiff > 0)

// Fill up data code word
PadData();

// Calculate error correction blocks
var codeWordWithECC = CalculateECCBlocks();

// Calculate interleaved code word lengths
var interleavedLength = CalculateInterleavedLength();

// Interleave code words
var interleavedData = InterleaveData();

// Place interleaved data on module matrix
var qrData = PlaceModules();

return qrData;


// fills the bit array with a repeating pattern to reach the required length
void PadData()
{
// set 'write index' to end of existing bit array
var index = bitArray.Length;
// extend bit array to required length
bitArray.Length = dataLength;
// pad with 4 zeros (or less if lengthDiff < 4)
index += Math.Min(lengthDiff, 4);
// pad to nearest 8 bit boundary
if ((uint)index % 8 != 0)
index += 8 - (int)((uint)index % 8);
// pad with repeating pattern
var repeatingPatternIndex = 0;
while (index < dataLength)
var dataLength = eccInfo.TotalDataCodewords * 8;
var lengthDiff = dataLength - bitArray.Length;
if (lengthDiff > 0)
{
bitArray[index++] = _repeatingPattern[repeatingPatternIndex++];
if (repeatingPatternIndex >= _repeatingPattern.Length)
repeatingPatternIndex = 0;
// set 'write index' to end of existing bit array
var index = bitArray.Length;
// extend bit array to required length
bitArray.Length = dataLength;
// pad with 4 zeros (or less if lengthDiff < 4)
index += Math.Min(lengthDiff, 4);
// pad to nearest 8 bit boundary
if ((uint)index % 8 != 0)
index += 8 - (int)((uint)index % 8);
// pad with repeating pattern
var repeatingPatternIndex = 0;
while (index < dataLength)
{
bitArray[index++] = _repeatingPattern[repeatingPatternIndex++];
if (repeatingPatternIndex >= _repeatingPattern.Length)
repeatingPatternIndex = 0;
}
}
}

// Generate the generator polynomial using the number of ECC words.
List<CodewordBlock> codeWordWithECC;
using (var generatorPolynom = CalculateGeneratorPolynom(eccInfo.ECCPerBlock))
List<CodewordBlock> CalculateECCBlocks()
{
//Calculate error correction words
codeWordWithECC = new List<CodewordBlock>(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2);
AddCodeWordBlocks(1, eccInfo.BlocksInGroup1, eccInfo.CodewordsInGroup1, 0, bitArray.Length, generatorPolynom);
int offset = eccInfo.BlocksInGroup1 * eccInfo.CodewordsInGroup1 * 8;
AddCodeWordBlocks(2, eccInfo.BlocksInGroup2, eccInfo.CodewordsInGroup2, offset, bitArray.Length - offset, generatorPolynom);
}
List<CodewordBlock> codewordBlocks;
// Generate the generator polynomial using the number of ECC words.
using (var generatorPolynom = CalculateGeneratorPolynom(eccInfo.ECCPerBlock))
{
//Calculate error correction words
codewordBlocks = new List<CodewordBlock>(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2);
AddCodeWordBlocks(1, eccInfo.BlocksInGroup1, eccInfo.CodewordsInGroup1, 0, bitArray.Length, generatorPolynom);
int offset = eccInfo.BlocksInGroup1 * eccInfo.CodewordsInGroup1 * 8;
AddCodeWordBlocks(2, eccInfo.BlocksInGroup2, eccInfo.CodewordsInGroup2, offset, bitArray.Length - offset, generatorPolynom);
return codewordBlocks;
}

//Calculate interleaved code word lengths
int interleavedLength = 0;
for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
{
foreach (var codeBlock in codeWordWithECC)
if ((uint)codeBlock.CodeWordsLength / 8 > i)
interleavedLength += 8;
}
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.ECCWords.Length > i)
interleavedLength += 8;
void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, int offset2, int count, Polynom generatorPolynom)
{
var groupLength = codewordsInGroup * 8;
for (var i = 0; i < blocksInGroup; i++)
{
var eccWordList = CalculateECCWords(bitArray, offset2, groupLength, eccInfo, generatorPolynom);
codewordBlocks.Add(new CodewordBlock(offset2, groupLength, eccWordList));
offset2 += groupLength;
}
}
}
interleavedLength += remainderBits[version - 1];

//Interleave code words
var interleavedData = new BitArray(interleavedLength);
int pos = 0;
for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
// Calculate the length of the interleaved data
int CalculateInterleavedLength()
{
foreach (var codeBlock in codeWordWithECC)
var length = 0;
for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
{
if ((uint)codeBlock.CodeWordsLength / 8 > i)
pos = bitArray.CopyTo(interleavedData, (int)((uint)i * 8) + codeBlock.CodeWordsOffset, pos, 8);
foreach (var codeBlock in codeWordWithECC)
if ((uint)codeBlock.CodeWordsLength / 8 > i)
length += 8;
}
}
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.ECCWords.Length > i)
pos = DecToBin(codeBlock.ECCWords[i], 8, interleavedData, pos);
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.ECCWords.Length > i)
length += 8;
}
length += remainderBits[version - 1];
return length;
}

//Place interleaved data on module matrix
var qr = new QRCodeData(version);
var blockedModules = new List<Rectangle>(17);
ModulePlacer.PlaceFinderPatterns(qr, blockedModules);
ModulePlacer.ReserveSeperatorAreas(qr.ModuleMatrix.Count, blockedModules);
ModulePlacer.PlaceAlignmentPatterns(qr, alignmentPatternTable[version].PatternPositions, blockedModules);
ModulePlacer.PlaceTimingPatterns(qr, blockedModules);
ModulePlacer.PlaceDarkModule(qr, version, blockedModules);
ModulePlacer.ReserveVersionAreas(qr.ModuleMatrix.Count, version, blockedModules);
ModulePlacer.PlaceDataWords(qr, interleavedData, blockedModules);
var maskVersion = ModulePlacer.MaskCode(qr, version, blockedModules, eccLevel);
var formatStr = GetFormatString(eccLevel, maskVersion);

ModulePlacer.PlaceFormat(qr, formatStr);
if (version >= 7)
// Interleave the data
BitArray InterleaveData()
{
var versionString = GetVersionString(version);
ModulePlacer.PlaceVersion(qr, versionString);
}

var data = new BitArray(interleavedLength);
int pos = 0;
for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++)
{
foreach (var codeBlock in codeWordWithECC)
{
if ((uint)codeBlock.CodeWordsLength / 8 > i)
pos = bitArray.CopyTo(data, (int)((uint)i * 8) + codeBlock.CodeWordsOffset, pos, 8);
}
}
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.ECCWords.Length > i)
pos = DecToBin(codeBlock.ECCWords[i], 8, data, pos);
}

ModulePlacer.AddQuietZone(qr);
return qr;
return data;
}

void AddCodeWordBlocks(int blockNum, int blocksInGroup, int codewordsInGroup, int offset2, int count, Polynom generatorPolynom)
// Place the modules on the QR code matrix
QRCodeData PlaceModules()
{
var groupLength = codewordsInGroup * 8;
for (var i = 0; i < blocksInGroup; i++)
var qr = new QRCodeData(version);
var blockedModules = new List<Rectangle>(17);
ModulePlacer.PlaceFinderPatterns(qr, blockedModules);
ModulePlacer.ReserveSeperatorAreas(qr.ModuleMatrix.Count, blockedModules);
ModulePlacer.PlaceAlignmentPatterns(qr, alignmentPatternTable[version].PatternPositions, blockedModules);
ModulePlacer.PlaceTimingPatterns(qr, blockedModules);
ModulePlacer.PlaceDarkModule(qr, version, blockedModules);
ModulePlacer.ReserveVersionAreas(qr.ModuleMatrix.Count, version, blockedModules);
ModulePlacer.PlaceDataWords(qr, interleavedData, blockedModules);
var maskVersion = ModulePlacer.MaskCode(qr, version, blockedModules, eccLevel);
var formatStr = GetFormatString(eccLevel, maskVersion);

ModulePlacer.PlaceFormat(qr, formatStr);
if (version >= 7)
{
var eccWordList = CalculateECCWords(bitArray, offset2, groupLength, eccInfo, generatorPolynom);
codeWordWithECC.Add(new CodewordBlock(offset2, groupLength, eccWordList));
offset2 += groupLength;
var versionString = GetVersionString(version);
ModulePlacer.PlaceVersion(qr, versionString);
}

ModulePlacer.AddQuietZone(qr);

return qr;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,7 @@ namespace QRCoder
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public enum ECCLevel
{
Default = -1,
L = 0,
M = 1,
Q = 2,
Expand Down
1 change: 1 addition & 0 deletions QRCoderApiTests/net60-windows/QRCoder.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ namespace QRCoder
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public enum ECCLevel
{
Default = -1,
L = 0,
M = 1,
Q = 2,
Expand Down
1 change: 1 addition & 0 deletions QRCoderApiTests/net60/QRCoder.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,7 @@ namespace QRCoder
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public enum ECCLevel
{
Default = -1,
L = 0,
M = 1,
Q = 2,
Expand Down
1 change: 1 addition & 0 deletions QRCoderApiTests/netstandard13/QRCoder.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ namespace QRCoder
public static QRCoder.QRCodeData GenerateQrCode(string plainText, QRCoder.QRCodeGenerator.ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, QRCoder.QRCodeGenerator.EciMode eciMode = 0, int requestedVersion = -1) { }
public enum ECCLevel
{
Default = -1,
L = 0,
M = 1,
Q = 2,
Expand Down
Loading

0 comments on commit b111fd0

Please sign in to comment.