diff --git a/QRCoder/QRCodeData.cs b/QRCoder/QRCodeData.cs index 37ee29a5..35e6d120 100644 --- a/QRCoder/QRCodeData.cs +++ b/QRCoder/QRCodeData.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using System.IO.Compression; namespace QRCoder; @@ -119,6 +120,8 @@ public QRCodeData(byte[] rawData, Compression compressMode) ModuleMatrix[y][x] = modules.Dequeue(); } } + + Debug.Assert(modules.Count < 8); } /// @@ -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(); + } + + /// + /// Saves the raw data of the QR code to a specified file with the specified compression mode. + /// + /// The path to the file where the raw data will be saved. + /// The compression mode used for the raw data. + 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); @@ -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(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(); } - /// - /// Saves the raw data of the QR code to a specified file with the specified compression mode. - /// - /// The path to the file where the raw data will be saved. - /// The compression mode used for the raw data. - public void SaveRawData(string filePath, Compression compressMode) - => File.WriteAllBytes(filePath, GetRawData(compressMode)); - /// /// Gets the version of the QR code. /// diff --git a/QRCoderTests/QRGeneratorTests.cs b/QRCoderTests/QRGeneratorTests.cs index 969d6947..1220df7d 100644 --- a/QRCoderTests/QRGeneratorTests.cs +++ b/QRCoderTests/QRGeneratorTests.cs @@ -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)] @@ -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); } }