-
-
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 3 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,44 @@ 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) | ||||||||||||||
| var count = rawData.Length; | ||||||||||||||
|
|
||||||||||||||
| if (count < 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); | ||||||||||||||
| var sideLen = (int)rawData[4]; | ||||||||||||||
| Version = (sideLen - 21 - 8) / 4 + 1; | ||||||||||||||
|
|
||||||||||||||
| //Unpack | ||||||||||||||
| var modules = new Queue<bool>(8 * bytes.Count); | ||||||||||||||
| foreach (var b in bytes) | ||||||||||||||
| var modules = new Queue<bool>(8 * (count - 5)); | ||||||||||||||
| for (int j = 5; j < count; j++) | ||||||||||||||
| { | ||||||||||||||
| var b = rawData[j]; | ||||||||||||||
| for (int i = 7; i >= 0; i--) | ||||||||||||||
| { | ||||||||||||||
| modules.Enqueue((b & (1 << i)) != 0); | ||||||||||||||
|
|
@@ -111,7 +113,6 @@ public QRCodeData(byte[] rawData, Compression compressMode) | |||||||||||||
| ModuleMatrix[y][x] = modules.Dequeue(); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| /// <summary> | ||||||||||||||
|
|
@@ -121,60 +122,64 @@ 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 }); | ||||||||||||||
|
|
||||||||||||||
| //Add header - rowsize | ||||||||||||||
| bytes.Add((byte)ModuleMatrix.Count); | ||||||||||||||
| using var output = new MemoryStream(); | ||||||||||||||
| Stream targetStream = output; | ||||||||||||||
| DeflateStream? deflateStream = null; | ||||||||||||||
| GZipStream? gzipStream = null; | ||||||||||||||
|
|
||||||||||||||
| //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") | ||||||||||||||
| 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. |
||||||||||||||
|
|
||||||||||||||
| //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); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| for (int i = 0; i < 8 - (ModuleMatrix.Count * ModuleMatrix.Count) % 8; i++) | ||||||||||||||
|
Shane32 marked this conversation as resolved.
Outdated
|
||||||||||||||
| { | ||||||||||||||
| 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.
To give the JIT some hints, so the bound checks in the array access below can be removed (at least on newer JIT versions), and there's no harm for older ones.
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.
I removed count and replaced with
rawData.Lengthall 3 times - please verify.