Skip to content
Merged
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
207 changes: 9 additions & 198 deletions QRCoder/QRCodeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#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 @@ -368,7 +365,7 @@ List<CodewordBlock> CalculateECCBlocks()
{
List<CodewordBlock> codewordBlocks;
// Generate the generator polynomial using the number of ECC words.
using (var generatorPolynom = CalculateGeneratorPolynom(eccInfo.ECCPerBlock))
using (var generatorPolynom = Polynom.CreateGeneratorPolynom(eccInfo.ECCPerBlock))
{
//Calculate error correction words
codewordBlocks = CodewordBlock.GetList(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2);
Expand Down Expand Up @@ -699,7 +696,7 @@ private static ArraySegment<byte> CalculateECCWords(BitArray bitArray, int offse
// Convert the first coefficient to its corresponding alpha exponent unless it's zero.
// Coefficients that are zero remain zero because log(0) is undefined.
var index0Coefficient = leadTermSource[0].Coefficient;
index0Coefficient = index0Coefficient == 0 ? 0 : GaloisField.GetAlphaExpFromIntVal(index0Coefficient);
index0Coefficient = GaloisField.GetAlphaExpFromIntVal((byte)index0Coefficient);
var alphaNotation = new PolynomItem(index0Coefficient, leadTermSource[0].Exponent);
var resPoly = MultiplyGeneratorPolynomByLeadterm(generatorPolynom, alphaNotation, i);
ConvertToDecNotationInPlace(resPoly);
Expand Down Expand Up @@ -742,7 +739,7 @@ private static void ConvertToDecNotationInPlace(Polynom poly)
for (var i = 0; i < poly.Count; i++)
{
// Convert the alpha exponent of the coefficient to its decimal value and create a new polynomial item with the updated coefficient.
poly[i] = new PolynomItem(GaloisField.GetIntValFromAlphaExp(poly[i].Coefficient), poly[i].Exponent);
poly[i] = new PolynomItem(GaloisField.GetIntValFromAlphaExp((byte)poly[i].Coefficient), poly[i].Exponent);
}
}

Expand Down Expand Up @@ -816,55 +813,25 @@ private static Polynom CalculateMessagePolynom(BitArray bitArray, int offset, in
// Convert each 8-bit segment into a decimal value and add it to the polynomial
for (int i = 0; i < polynomLength; i++)
{
messagePol.Add(new PolynomItem(BinToDec(bitArray, offset, 8), exponent--));
messagePol.Add(new PolynomItem(BinToDec(bitArray, offset), exponent--));
offset += 8;
}

return messagePol;
}

/// <summary>
/// Calculates the generator polynomial used for creating error correction codewords.
/// </summary>
/// <param name="numEccWords">The number of error correction codewords to generate.</param>
/// <returns>A polynomial that can be used to generate ECC codewords.</returns>
private static Polynom CalculateGeneratorPolynom(int numEccWords)
{
var generatorPolynom = new Polynom(2); // Start with the simplest form of the polynomial
generatorPolynom.Add(new PolynomItem(0, 1));
generatorPolynom.Add(new PolynomItem(0, 0));

using (var multiplierPolynom = new Polynom(numEccWords * 2)) // Used for polynomial multiplication
{
for (var i = 1; i <= numEccWords - 1; i++)
{
// Clear and set up the multiplier polynomial for the current multiplication
multiplierPolynom.Clear();
multiplierPolynom.Add(new PolynomItem(0, 1));
multiplierPolynom.Add(new PolynomItem(i, 0));

// Multiply the generator polynomial by the current multiplier polynomial
var newGeneratorPolynom = MultiplyAlphaPolynoms(generatorPolynom, multiplierPolynom);
generatorPolynom.Dispose();
generatorPolynom = newGeneratorPolynom;
}
}

return generatorPolynom; // Return the completed generator polynomial
}

/// <summary>
/// Converts a segment of a BitArray into its decimal (integer) equivalent.
/// </summary>
/// <returns>The integer value that represents the specified binary data.</returns>
private static int BinToDec(BitArray bitArray, int offset, int count)
private static byte BinToDec(BitArray bitArray, int offset)
{
var ret = 0;
for (int i = 0; i < count; i++)
for (int i = 0; i < 8; i++)
{
ret ^= bitArray[offset + i] ? 1 << (count - i - 1) : 0;
ret ^= bitArray[offset + i] ? 1 << (7 - i) : 0;
}
return ret;
return (byte)ret;
}

/// <summary>
Expand Down Expand Up @@ -1083,8 +1050,7 @@ private static Polynom XORPolynoms(Polynom messagePolynom, Polynom resPolynom)
for (var i = 1; i < longPoly.Count; i++)
{
var polItemRes = new PolynomItem(
longPoly[i].Coefficient ^
(shortPoly.Count > i ? shortPoly[i].Coefficient : 0),
longPoly[i].Coefficient ^ (shortPoly.Count > i ? shortPoly[i].Coefficient : 0),
messagePolynom[0].Exponent - i
);
resultPolynom.Add(polItemRes);
Expand All @@ -1103,7 +1069,6 @@ private static Polynom MultiplyGeneratorPolynomByLeadterm(Polynom genPolynom, Po
foreach (var polItemBase in genPolynom)
{
var polItemRes = new PolynomItem(

(polItemBase.Coefficient + leadTerm.Coefficient) % 255,
polItemBase.Exponent - lowerExponentBy
);
Expand All @@ -1112,160 +1077,6 @@ private static Polynom MultiplyGeneratorPolynomByLeadterm(Polynom genPolynom, Po
return resultPolynom;
}

/// <summary>
/// Multiplies two polynomials, treating coefficients as exponents of a primitive element (alpha), which is common in error correction algorithms such as Reed-Solomon.
/// </summary>
/// <param name="polynomBase">The first polynomial to multiply.</param>
/// <param name="polynomMultiplier">The second polynomial to multiply.</param>
/// <returns>A new polynomial which is the result of the multiplication of the two input polynomials.</returns>
private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polynomMultiplier)
{
// Initialize a new polynomial with a size based on the product of the sizes of the two input polynomials.
var resultPolynom = new Polynom(polynomMultiplier.Count * polynomBase.Count);

// Multiply each term of the first polynomial by each term of the second polynomial.
foreach (var polItemBase in polynomMultiplier)
{
foreach (var polItemMulti in polynomBase)
{
// Create a new polynomial term with the coefficients added (as exponents) and exponents summed.
var polItemRes = new PolynomItem
(
GaloisField.ShrinkAlphaExp(polItemBase.Coefficient + polItemMulti.Coefficient),
(polItemBase.Exponent + polItemMulti.Exponent)
);
resultPolynom.Add(polItemRes);
}
}

// Identify and merge terms with the same exponent.
#if NET5_0_OR_GREATER
var toGlue = GetNotUniqueExponents(resultPolynom, resultPolynom.Count <= 128 ? stackalloc int[128].Slice(0, resultPolynom.Count) : new int[resultPolynom.Count]);
var gluedPolynoms = toGlue.Length <= 128
? stackalloc PolynomItem[128].Slice(0, toGlue.Length)
: new PolynomItem[toGlue.Length];
#else
var toGlue = GetNotUniqueExponents(resultPolynom);
var gluedPolynoms = new PolynomItem[toGlue.Length];
#endif
var gluedPolynomsIndex = 0;
foreach (var exponent in toGlue)
{
var coefficient = 0;
foreach (var polynomOld in resultPolynom)
{
if (polynomOld.Exponent == exponent)
coefficient ^= GaloisField.GetIntValFromAlphaExp(polynomOld.Coefficient);
}

// Fix the polynomial terms by recalculating the coefficients based on XORed results.
var polynomFixed = new PolynomItem(GaloisField.GetAlphaExpFromIntVal(coefficient), exponent);
gluedPolynoms[gluedPolynomsIndex++] = polynomFixed;
}

// Remove duplicated exponents and add the corrected ones back.
for (int i = resultPolynom.Count - 1; i >= 0; i--)
#if NET5_0_OR_GREATER
if (toGlue.Contains(resultPolynom[i].Exponent))
#else
if (Array.IndexOf(toGlue, resultPolynom[i].Exponent) >= 0)
#endif
resultPolynom.RemoveAt(i);
foreach (var polynom in gluedPolynoms)
resultPolynom.Add(polynom);

// Sort the polynomial terms by exponent in descending order.
resultPolynom.Sort((x, y) => -x.Exponent.CompareTo(y.Exponent));
return resultPolynom;

// Auxiliary function to identify exponents that appear more than once in the polynomial.
#if NET5_0_OR_GREATER
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;
foreach (var row in list)
{
buffer[idx++] = row.Exponent;
}

buffer.Sort();

idx = 0;
int expCount = 0;
int last = buffer[0];

for (int i = 1; i < buffer.Length; ++i)
{
if (buffer[i] == last)
{
expCount++;
}
else
{
if (expCount > 0)
{
Debug.Assert(idx <= i - 1);

buffer[idx++] = last;
expCount = 0;
}
}

last = buffer[i];
}

return buffer.Slice(0, idx);
}
#else
static int[] GetNotUniqueExponents(Polynom list)
{
var dic = new Dictionary<int, bool>(list.Count);
foreach (var row in list)
{
#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
if (!dic.TryAdd(row.Exponent, false))
#else
if (!dic.ContainsKey(row.Exponent))
dic.Add(row.Exponent, false);
else
#endif
dic[row.Exponent] = true;
}

// Collect all exponents that appeared more than once.
int count = 0;
foreach (var row in dic)
{
if (row.Value)
count++;
}

var result = new int[count];
int i = 0;
foreach (var row in dic)
{
if (row.Value)
result[i++] = row.Key;
}

return result;
}
#endif
}

/// <inheritdoc cref="IDisposable.Dispose"/>
public virtual void Dispose()
{
Expand Down
6 changes: 5 additions & 1 deletion QRCoder/QRCodeGenerator/ECCInfo.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using System.Diagnostics;

namespace QRCoder;

public partial class QRCodeGenerator
{
/// <summary>
/// Represents the error correction coding (ECC) information for a specific version and error correction level of a QR code.
/// </summary>
private struct ECCInfo
private readonly struct ECCInfo
{
/// <summary>
/// Initializes a new instance of the ECCInfo struct with specified properties.
Expand All @@ -21,6 +23,7 @@ private struct ECCInfo
public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int eccPerBlock, int blocksInGroup1,
int codewordsInGroup1, int blocksInGroup2, int codewordsInGroup2)
{
Debug.Assert(eccPerBlock is > 0 and < 32);
Version = version;
ErrorCorrectionLevel = errorCorrectionLevel;
TotalDataCodewords = totalDataCodewords;
Expand All @@ -42,6 +45,7 @@ public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodeword
/// <param name="eccPerBlock">The number of error correction codewords per block.</param>
public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int totalDataBits, int eccPerBlock)
{
Debug.Assert(eccPerBlock is > 0 and < 32);
Version = version;
ErrorCorrectionLevel = errorCorrectionLevel;
TotalDataCodewords = totalDataCodewords;
Expand Down
34 changes: 8 additions & 26 deletions QRCoder/QRCodeGenerator/GaloisField.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Diagnostics;

namespace QRCoder;

public partial class QRCodeGenerator
Expand All @@ -16,47 +14,31 @@ public partial class QRCodeGenerator
internal static class GaloisField
{
#if HAS_SPAN
internal static ReadOnlySpan<byte> _galoisFieldByExponentAlpha =>
internal static ReadOnlySpan<byte> _integerValueByAlphaExponent =>
#else
internal static readonly byte[] _galoisFieldByExponentAlpha =
internal static readonly byte[] _integerValueByAlphaExponent =
#endif
[1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142, 1];

#if HAS_SPAN
internal static ReadOnlySpan<byte> _galoisFieldByIntegerValue =>
internal static ReadOnlySpan<byte> _alphaExponentByIntegerValue =>
#else
internal static readonly byte[] _galoisFieldByIntegerValue =
internal static readonly byte[] _alphaExponentByIntegerValue =
#endif
[0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175];

/// <summary>
/// Retrieves the integer value from the Galois field that corresponds to a given exponent.
/// This is used in Reed-Solomon and other error correction calculations involving Galois fields.
/// </summary>
public static int GetIntValFromAlphaExp(int exp)
=> _galoisFieldByExponentAlpha[exp];
public static byte GetIntValFromAlphaExp(byte exp)
=> _integerValueByAlphaExponent[exp];

/// <summary>
/// Retrieves the exponent from the Galois field that corresponds to a given integer value.
/// Throws an exception if the integer value is zero, as zero does not have a logarithmic representation in the field.
/// </summary>
public static int GetAlphaExpFromIntVal(int intVal)
{
if (intVal == 0)
ThrowIntValOutOfRangeException(); // Zero is not valid as it does not have an exponent representation.
return _galoisFieldByIntegerValue[intVal];

void ThrowIntValOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(intVal), "The provided integer value is out of range, as zero is not representable.");
}

/// <summary>
/// Normalizes a Galois field exponent to ensure it remains within the bounds of the field's size.
/// 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)
{
Debug.Assert(alphaExp >= 0);
return (int)((uint)alphaExp % 256 + (uint)alphaExp / 256);
}
public static byte GetAlphaExpFromIntVal(byte intVal)
=> _alphaExponentByIntegerValue[intVal];
Comment thread
Shane32 marked this conversation as resolved.
}
}
Loading
Loading