-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Optimize compression/decompression and add tests #611
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6839f2d
518c84e
b6afcf5
0860e08
bf79ddf
1ff02b6
46ca871
cc12a82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -59,42 +59,54 @@ public QRCodeData(string pathToRawData, Compression compressMode) : this(File.Re | |||||||||||||
| /// <param name="compressMode">The compression mode used for the raw data.</param> | ||||||||||||||
| public QRCodeData(byte[] rawData, Compression compressMode) | ||||||||||||||
| { | ||||||||||||||
| var bytes = new List<byte>(rawData); | ||||||||||||||
|
|
||||||||||||||
| //Decompress | ||||||||||||||
| if (compressMode == Compression.Deflate) | ||||||||||||||
| { | ||||||||||||||
| using var input = new MemoryStream(bytes.ToArray()); | ||||||||||||||
| using var input = new MemoryStream(rawData); | ||||||||||||||
| using var output = new MemoryStream(); | ||||||||||||||
| using (var dstream = new DeflateStream(input, CompressionMode.Decompress)) | ||||||||||||||
| { | ||||||||||||||
| dstream.CopyTo(output); | ||||||||||||||
| } | ||||||||||||||
| bytes = new List<byte>(output.ToArray()); | ||||||||||||||
| rawData = output.ToArray(); | ||||||||||||||
| } | ||||||||||||||
| else if (compressMode == Compression.GZip) | ||||||||||||||
| { | ||||||||||||||
| using var input = new MemoryStream(bytes.ToArray()); | ||||||||||||||
| using var input = new MemoryStream(rawData); | ||||||||||||||
| using var output = new MemoryStream(); | ||||||||||||||
| using (var dstream = new GZipStream(input, CompressionMode.Decompress)) | ||||||||||||||
| { | ||||||||||||||
| dstream.CopyTo(output); | ||||||||||||||
| } | ||||||||||||||
| bytes = new List<byte>(output.ToArray()); | ||||||||||||||
| rawData = output.ToArray(); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if (bytes[0] != 0x51 || bytes[1] != 0x52 || bytes[2] != 0x52) | ||||||||||||||
| if (rawData.Length < 5) | ||||||||||||||
| throw new Exception("Invalid raw data file. File too short."); | ||||||||||||||
| if (rawData[0] != 0x51 || rawData[1] != 0x52 || rawData[2] != 0x52) | ||||||||||||||
| throw new Exception("Invalid raw data file. Filetype doesn't match \"QRR\"."); | ||||||||||||||
|
|
||||||||||||||
| //Set QR code version | ||||||||||||||
| var sideLen = (int)bytes[4]; | ||||||||||||||
| bytes.RemoveRange(0, 5); | ||||||||||||||
| Version = (sideLen - 21 - 8) / 4 + 1; | ||||||||||||||
| // Set QR code version from side length (includes 8-module quiet zone) | ||||||||||||||
| var sideLen = (int)rawData[4]; | ||||||||||||||
| if (sideLen < 29) // Micro QR: sideLen = 19 + 2*(m-1), m in [1..4] -> versions -1..-4 | ||||||||||||||
| { | ||||||||||||||
| if (((sideLen - 19) & 1) != 0) | ||||||||||||||
| throw new Exception("Invalid raw data file. Side length not valid for Micro QR."); | ||||||||||||||
| var m = ((sideLen - 19) / 2) + 1; | ||||||||||||||
| Version = -m; | ||||||||||||||
| } | ||||||||||||||
| else // Standard QR: sideLen = 29 + 4*(v-1), v in [1..40] | ||||||||||||||
| { | ||||||||||||||
| if (((sideLen - 29) % 4) != 0) | ||||||||||||||
| throw new Exception("Invalid raw data file. Side length not valid for QR."); | ||||||||||||||
| Version = ((sideLen - 29) / 4) + 1; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| //Unpack | ||||||||||||||
| var modules = new Queue<bool>(8 * bytes.Count); | ||||||||||||||
| foreach (var b in bytes) | ||||||||||||||
| var modules = new Queue<bool>(8 * (rawData.Length - 5)); | ||||||||||||||
| for (int j = 5; j < rawData.Length; j++) | ||||||||||||||
| { | ||||||||||||||
| var b = rawData[j]; | ||||||||||||||
| for (int i = 7; i >= 0; i--) | ||||||||||||||
| { | ||||||||||||||
| modules.Enqueue((b & (1 << i)) != 0); | ||||||||||||||
|
|
@@ -111,7 +123,6 @@ public QRCodeData(byte[] rawData, Compression compressMode) | |||||||||||||
| ModuleMatrix[y][x] = modules.Dequeue(); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// <summary> | ||||||||||||||
|
|
@@ -121,60 +132,69 @@ public QRCodeData(byte[] rawData, Compression compressMode) | |||||||||||||
| /// <returns>Returns the raw data of the QR code as a byte array.</returns> | ||||||||||||||
| public byte[] GetRawData(Compression compressMode) | ||||||||||||||
| { | ||||||||||||||
| var bytes = new List<byte>(); | ||||||||||||||
|
|
||||||||||||||
| //Add header - signature ("QRR") | ||||||||||||||
| bytes.AddRange(new byte[] { 0x51, 0x52, 0x52, 0x00 }); | ||||||||||||||
| using var output = new MemoryStream(); | ||||||||||||||
| Stream targetStream = output; | ||||||||||||||
| DeflateStream? deflateStream = null; | ||||||||||||||
| GZipStream? gzipStream = null; | ||||||||||||||
|
|
||||||||||||||
| //Add header - rowsize | ||||||||||||||
| bytes.Add((byte)ModuleMatrix.Count); | ||||||||||||||
|
|
||||||||||||||
| //Build data queue | ||||||||||||||
| var dataQueue = new Queue<int>(); | ||||||||||||||
| foreach (var row in ModuleMatrix) | ||||||||||||||
| //Set up compression stream if needed | ||||||||||||||
| if (compressMode == Compression.Deflate) | ||||||||||||||
| { | ||||||||||||||
| foreach (var module in row) | ||||||||||||||
| { | ||||||||||||||
| dataQueue.Enqueue((bool)module ? 1 : 0); | ||||||||||||||
| } | ||||||||||||||
| deflateStream = new DeflateStream(output, CompressionMode.Compress, true); | ||||||||||||||
| targetStream = deflateStream; | ||||||||||||||
| } | ||||||||||||||
| for (int i = 0; i < 8 - (ModuleMatrix.Count * ModuleMatrix.Count) % 8; i++) | ||||||||||||||
| else if (compressMode == Compression.GZip) | ||||||||||||||
| { | ||||||||||||||
| dataQueue.Enqueue(0); | ||||||||||||||
| gzipStream = new GZipStream(output, CompressionMode.Compress, true); | ||||||||||||||
| targetStream = gzipStream; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| //Process queue | ||||||||||||||
| while (dataQueue.Count > 0) | ||||||||||||||
| try | ||||||||||||||
| { | ||||||||||||||
| byte b = 0; | ||||||||||||||
| for (int i = 7; i >= 0; i--) | ||||||||||||||
| //Add header - signature ("QRR") | ||||||||||||||
| #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 | ||||||||||||||
| targetStream.Write([0x51, 0x52, 0x52, 0x00]); | ||||||||||||||
| #else | ||||||||||||||
| targetStream.Write(new byte[] { 0x51, 0x52, 0x52, 0x00 }, 0, 4); | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use collection expression to avoid the
Suggested change
I thinks .NET 8+ is finde, as older targets aren't supported anymore by .NET. Also .NET 10's JIT would stack allocate that array, so no heap allocation.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I bumped the C# version to 12, so the code you supplied will work as far back as .NET Core 2.1 -- anything supported by Span. I'll update the conditional again to HAS_SPAN in a future PR, and probably add netstandard2_1 as a target at the same time. |
||||||||||||||
| #endif | ||||||||||||||
|
|
||||||||||||||
| //Add header - rowsize | ||||||||||||||
| targetStream.WriteByte((byte)ModuleMatrix.Count); | ||||||||||||||
|
|
||||||||||||||
| //Build data queue | ||||||||||||||
| var dataQueue = new Queue<int>(); | ||||||||||||||
| foreach (var row in ModuleMatrix) | ||||||||||||||
| { | ||||||||||||||
| b += (byte)(dataQueue.Dequeue() << i); | ||||||||||||||
| foreach (var module in row) | ||||||||||||||
| { | ||||||||||||||
| dataQueue.Enqueue((bool)module ? 1 : 0); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| int mod = (int)(((uint)ModuleMatrix.Count * (uint)ModuleMatrix.Count) % 8); | ||||||||||||||
| for (int i = 0; i < 8 - mod; i++) | ||||||||||||||
| { | ||||||||||||||
| dataQueue.Enqueue(0); | ||||||||||||||
| } | ||||||||||||||
| bytes.Add(b); | ||||||||||||||
| } | ||||||||||||||
| var rawData = bytes.ToArray(); | ||||||||||||||
|
|
||||||||||||||
| //Compress stream (optional) | ||||||||||||||
| if (compressMode == Compression.Deflate) | ||||||||||||||
| { | ||||||||||||||
| using var output = new MemoryStream(); | ||||||||||||||
| using (var dstream = new DeflateStream(output, CompressionMode.Compress)) | ||||||||||||||
| //Process queue | ||||||||||||||
| while (dataQueue.Count > 0) | ||||||||||||||
| { | ||||||||||||||
| dstream.Write(rawData, 0, rawData.Length); | ||||||||||||||
| byte b = 0; | ||||||||||||||
| for (int i = 7; i >= 0; i--) | ||||||||||||||
| { | ||||||||||||||
| b += (byte)(dataQueue.Dequeue() << i); | ||||||||||||||
| } | ||||||||||||||
| targetStream.WriteByte(b); | ||||||||||||||
| } | ||||||||||||||
| rawData = output.ToArray(); | ||||||||||||||
| } | ||||||||||||||
| else if (compressMode == Compression.GZip) | ||||||||||||||
| finally | ||||||||||||||
| { | ||||||||||||||
| using var output = new MemoryStream(); | ||||||||||||||
| using (var gzipStream = new GZipStream(output, CompressionMode.Compress, true)) | ||||||||||||||
| { | ||||||||||||||
| gzipStream.Write(rawData, 0, rawData.Length); | ||||||||||||||
| } | ||||||||||||||
| rawData = output.ToArray(); | ||||||||||||||
| //Close compression streams to flush data | ||||||||||||||
| deflateStream?.Dispose(); | ||||||||||||||
| gzipStream?.Dispose(); | ||||||||||||||
| } | ||||||||||||||
| return rawData; | ||||||||||||||
|
|
||||||||||||||
| return output.ToArray(); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// <summary> | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just as note: for .NET less than 8 this will still allocate the array, as the collection builder, etc. isn't available in .NET, so Roslyn can't use these types and produces "fallback-code".
The change is still good, as it clearer to read. Thanks.