Skip to content
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
83 changes: 52 additions & 31 deletions QRCoder/QRCodeData.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using System.IO.Compression;

namespace QRCoder;
Expand Down Expand Up @@ -119,6 +120,8 @@ public QRCodeData(byte[] rawData, Compression compressMode)
ModuleMatrix[y][x] = modules.Dequeue();
}
}

Debug.Assert(modules.Count < 8);
}

/// <summary>
Expand All @@ -129,11 +132,30 @@ public QRCodeData(byte[] rawData, Compression compressMode)
public byte[] GetRawData(Compression compressMode)
{
using var output = new MemoryStream();

WriteRawData(output, compressMode);

return output.ToArray();
}

/// <summary>
/// Saves the raw data of the QR code to a specified file with the specified compression mode.
/// </summary>
/// <param name="filePath">The path to the file where the raw data will be saved.</param>
/// <param name="compressMode">The compression mode used for the raw data.</param>
public void SaveRawData(string filePath, Compression compressMode)
{
using var output = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
WriteRawData(output, compressMode);
}

private void WriteRawData(Stream output, Compression compressMode)
{
Stream targetStream = output;
DeflateStream? deflateStream = null;
GZipStream? gzipStream = null;

//Set up compression stream if needed
// Set up compression stream if needed
if (compressMode == Compression.Deflate)
{
deflateStream = new DeflateStream(output, CompressionMode.Compress, true);
Expand All @@ -147,61 +169,60 @@ public byte[] GetRawData(Compression compressMode)

try
{
//Add header - signature ("QRR")
// Add header - signature ("QRR")
#if HAS_SPAN
targetStream.Write([0x51, 0x52, 0x52, 0x00]);
#else
targetStream.Write(new byte[] { 0x51, 0x52, 0x52, 0x00 }, 0, 4);
targetStream.Write([0x51, 0x52, 0x52, 0x00], 0, 4);
#endif

//Add header - rowsize
// Add header - rowsize
targetStream.WriteByte((byte)ModuleMatrix.Count);

//Build data queue
var capacity = ModuleMatrix.Count * ModuleMatrix.Count + 7; // Total modules + max padding for byte alignment
var dataQueue = new Queue<int>(capacity);
// Write data one byte at a time
int data = 0;
int bits = 0;

foreach (var row in ModuleMatrix)
{
for (int i = 0; i < row.Length; i++)
{
dataQueue.Enqueue(row[i] ? 1 : 0);
data <<= 1;

if (row[i])
{
data += 1;
}

bits++;

if (bits == 8)
{
targetStream.WriteByte((byte)data);
data = 0;
bits = 0;
}
}
}
int mod = (int)(((uint)ModuleMatrix.Count * (uint)ModuleMatrix.Count) % 8);
for (int i = 0; i < 8 - mod; i++)
{
dataQueue.Enqueue(0);
}

//Process queue
while (dataQueue.Count > 0)
if (bits > 0)
{
byte b = 0;
for (int i = 7; i >= 0; i--)
{
b += (byte)(dataQueue.Dequeue() << i);
}
targetStream.WriteByte(b);
Debug.Assert(bits < 8);

data <<= 8 - bits;

targetStream.WriteByte((byte)data);
}
}
finally
{
//Close compression streams to flush data
// Close compression streams to flush data
deflateStream?.Dispose();
gzipStream?.Dispose();
}

return output.ToArray();
}

/// <summary>
/// Saves the raw data of the QR code to a specified file with the specified compression mode.
/// </summary>
/// <param name="filePath">The path to the file where the raw data will be saved.</param>
/// <param name="compressMode">The compression mode used for the raw data.</param>
public void SaveRawData(string filePath, Compression compressMode)
=> File.WriteAllBytes(filePath, GetRawData(compressMode));

/// <summary>
/// Gets the version of the QR code.
/// </summary>
Expand Down
31 changes: 27 additions & 4 deletions QRCoderTests/QRGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,30 @@ public void can_save_and_load_qrcode_data(QRCodeData.Compression compressionMode
reloadedMatrix.ShouldBe(originalMatrix);
}

[Theory]
[InlineData(QRCodeData.Compression.Uncompressed)]
[InlineData(QRCodeData.Compression.Deflate)]
[InlineData(QRCodeData.Compression.GZip)]
public void can_save_and_load_qrcode_data_to_file(QRCodeData.Compression compressionMode)
{
// Arrange - Create a QR code
var originalQrData = QRCodeGenerator.GenerateQrCode("https://github.com/Shane32/QRCoder", ECCLevel.H);
var originalMatrix = string.Join("", originalQrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray());
var filePath = System.IO.Path.GetTempFileName();

// Act - Save raw data and reload it
originalQrData.SaveRawData(filePath, compressionMode);
var reloadedQrData = new QRCodeData(filePath, compressionMode);
var reloadedMatrix = string.Join("", reloadedQrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray());

// Assert - Verify the data matches
reloadedQrData.Version.ShouldBe(originalQrData.Version);
reloadedQrData.ModuleMatrix.Count.ShouldBe(originalQrData.ModuleMatrix.Count);
reloadedMatrix.ShouldBe(originalMatrix);

System.IO.File.Delete(filePath);
}

[Theory]
[InlineData(QRCodeData.Compression.Uncompressed)]
[InlineData(QRCodeData.Compression.Deflate)]
Expand Down Expand Up @@ -887,14 +911,13 @@ public static class ExtensionMethods
{
public static string ToBitString(this BitArray bits)
{
var sb = new StringBuilder();
int bitLength = bits.Length;
char[] sb = new char[bitLength];
for (int i = 0; i < bitLength; i++)
{
char c = bits[i] ? '1' : '0';
sb.Append(c);
sb[i] = bits[i] ? '1' : '0';
}

return sb.ToString();
return new string(sb);
}
}
Loading