Skip to content
45 changes: 35 additions & 10 deletions QRCoder/QRCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,14 @@ public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, boo
//Version was passed as fixed version via parameter. Thus let's check if chosen version is valid.
if (minVersion > version)
{
var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding];
throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte);
// Use a throw-helper to avoid allocating a closure
Throw(eccLevel, encoding, version);

static void Throw(ECCLevel eccLevel, EncodingMode encoding, int version)
{
var maxSizeByte = CapacityTables.GetVersionInfo(version).Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding];
throw new Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte);
}
}
}

Expand Down Expand Up @@ -218,8 +224,9 @@ private static QRCodeData GenerateQrCode(BitArray bitArray, ECCLevel eccLevel, i
// Place interleaved data on module matrix
var qrData = PlaceModules();

return qrData;
CodewordBlock.ReturnList(codeWordWithECC);

return qrData;

// fills the bit array with a repeating pattern to reach the required length
void PadData()
Expand Down Expand Up @@ -255,7 +262,7 @@ List<CodewordBlock> CalculateECCBlocks()
using (var generatorPolynom = CalculateGeneratorPolynom(eccInfo.ECCPerBlock))
{
//Calculate error correction words
codewordBlocks = new List<CodewordBlock>(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2);
codewordBlocks = CodewordBlock.GetList(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);
Expand Down Expand Up @@ -287,7 +294,7 @@ int CalculateInterleavedLength()
for (var i = 0; i < eccInfo.ECCPerBlock; i++)
{
foreach (var codeBlock in codeWordWithECC)
if (codeBlock.ECCWords.Length > i)
if (codeBlock.ECCWords.Count > i)
length += 8;
}
length += CapacityTables.GetRemainderBits(version);
Expand All @@ -310,8 +317,8 @@ BitArray InterleaveData()
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);
if (codeBlock.ECCWords.Count > i)
pos = DecToBin(codeBlock.ECCWords.Array![i], 8, data, pos);
}

return data;
Expand Down Expand Up @@ -485,7 +492,7 @@ private static void GetVersionString(BitArray vStr, int version)
/// This method applies polynomial division, using the message polynomial and a generator polynomial,
/// to compute the remainder which forms the ECC codewords.
/// </summary>
private static byte[] CalculateECCWords(BitArray bitArray, int offset, int count, ECCInfo eccInfo, Polynom generatorPolynomBase)
private static ArraySegment<byte> CalculateECCWords(BitArray bitArray, int offset, int count, ECCInfo eccInfo, Polynom generatorPolynomBase)
{
var eccWords = eccInfo.ECCPerBlock;
// Calculate the message polynomial from the bit array data.
Expand Down Expand Up @@ -533,9 +540,16 @@ private static byte[] CalculateECCWords(BitArray bitArray, int offset, int count
generatorPolynom.Dispose();

// Convert the resulting polynomial into a byte array representing the ECC codewords.
var ret = new byte[leadTermSource.Count];
#if NETCOREAPP
var array = ArrayPool<byte>.Shared.Rent(leadTermSource.Count);
var ret = new ArraySegment<byte>(array, 0, leadTermSource.Count);
#else
var ret = new ArraySegment<byte>(new byte[leadTermSource.Count]);
var array = ret.Array!;
#endif

for (var i = 0; i < leadTermSource.Count; i++)
ret[i] = (byte)leadTermSource[i].Coefficient;
array[i] = (byte)leadTermSource[i].Coefficient;

// Free memory used by the message polynomial.
leadTermSource.Dispose();
Expand Down Expand Up @@ -1061,6 +1075,17 @@ private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polyno
#if NETCOREAPP
static ReadOnlySpan<int> GetNotUniqueExponents(Polynom list, Span<int> buffer)
{
// It works as follows:
// 1. a scratch buffer of the same size as the list is passed in
// 2. exponents are written / copied to that scratch buffer
// 3. scratch buffer is sorted, thus the exponents are in order
// 4. for each item in the scratch buffer (= ordered exponents) it's compared w/ the previous one
// * if equal, then increment a counter
// * else check if the counter is $>0$ and if so write the exponent to the result
//
// For writing the result the same scratch buffer is used, as by definition the index to write the result
// is `<=` the iteration index, so no overlap, etc. can occur.

Debug.Assert(list.Count == buffer.Length);

int idx = 0;
Expand Down
31 changes: 25 additions & 6 deletions QRCoder/QRCodeGenerator/CapacityTables.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;

Expand Down Expand Up @@ -36,7 +37,17 @@ private static class CapacityTables
/// block group details, and other parameters required for encoding error correction data.
/// </returns>
public static ECCInfo GetEccInfo(int version, ECCLevel eccLevel)
=> _capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel);
{
foreach (var item in _capacityECCTable)
{
if (item.Version == version && item.ErrorCorrectionLevel == eccLevel)
{
return item;
}
}

throw new InvalidOperationException("No item found"); // same exception type as Linq would throw
}

/// <summary>
/// Retrieves the capacity information for a specific QR code version.
Expand Down Expand Up @@ -92,11 +103,19 @@ public static int CalculateMinimumVersion(int length, EncodingMode encMode, ECCL
}

// if no version was found, throw an exception
var maxSizeByte = _capacityTable.Where(
x => x.Details.Any(
y => (y.ErrorCorrectionLevel == eccLevel))
).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]);
throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte);
// In order to get the maxSizeByte we use a throw-helper method to avoid the allocation of a closure
Throw(encMode, eccLevel);
throw null!; // this is needed to make the compiler happy

static void Throw(EncodingMode encMode, ECCLevel eccLevel)
{
var maxSizeByte = _capacityTable.Where(
x => x.Details.Any(
y => (y.ErrorCorrectionLevel == eccLevel))
).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]);

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

/// <summary>
Expand Down
31 changes: 28 additions & 3 deletions QRCoder/QRCodeGenerator/CodewordBlock.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
using System;
using System.Collections.Generic;
using System.Threading;

#if NETCOREAPP
using System.Buffers;
#endif

namespace QRCoder;

public partial class QRCodeGenerator
Expand All @@ -6,15 +14,15 @@ public partial class QRCodeGenerator
/// Represents a block of codewords in a QR code. QR codes are divided into several blocks for error correction purposes.
/// Each block contains a series of data codewords followed by error correction codewords.
/// </summary>
private struct CodewordBlock
private readonly struct CodewordBlock
{
/// <summary>
/// Initializes a new instance of the CodewordBlock struct with specified arrays of code words and error correction (ECC) words.
/// </summary>
/// <param name="codeWordsOffset">The offset of the data codewords within the main BitArray. Data codewords carry the actual information.</param>
/// <param name="codeWordsLength">The length in bits of the data codewords within the main BitArray.</param>
/// <param name="eccWords">The array of error correction codewords for this block. These codewords help recover the data if the QR code is damaged.</param>
public CodewordBlock(int codeWordsOffset, int codeWordsLength, byte[] eccWords)
public CodewordBlock(int codeWordsOffset, int codeWordsLength, ArraySegment<byte> eccWords)
{
CodeWordsOffset = codeWordsOffset;
CodeWordsLength = codeWordsLength;
Expand All @@ -34,6 +42,23 @@ public CodewordBlock(int codeWordsOffset, int codeWordsLength, byte[] eccWords)
/// <summary>
/// Gets the error correction codewords associated with this block.
/// </summary>
public byte[] ECCWords { get; }
public ArraySegment<byte> ECCWords { get; }

private static List<CodewordBlock>? _codewordBlocks;

public static List<CodewordBlock> GetList(int capacity)
=> Interlocked.Exchange(ref _codewordBlocks, null) ?? new List<CodewordBlock>(capacity);

public static void ReturnList(List<CodewordBlock> list)
{
#if NETCOREAPP
foreach (var item in list)
{
ArrayPool<byte>.Shared.Return(item.ECCWords.Array!);
}
#endif
list.Clear();
Interlocked.CompareExchange(ref _codewordBlocks, list, null);
}
}
}
6 changes: 5 additions & 1 deletion QRCoder/QRCodeGenerator/GaloisField.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;

namespace QRCoder;

Expand Down Expand Up @@ -43,6 +44,9 @@ public static int GetAlphaExpFromIntVal(int intVal)
/// This is particularly necessary when performing multiplications in the field which can result in exponents exceeding the field's maximum.
/// </summary>
public static int ShrinkAlphaExp(int alphaExp)
=> (alphaExp % 256) + (alphaExp / 256);
{
Debug.Assert(alphaExp >= 0);
return (int)((uint)alphaExp % 256 + (uint)alphaExp / 256);
}
}
}
Loading