From ea16006351e6a6bf15ee9080f80e333e2011187d Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 1 Oct 2025 16:35:06 +0100 Subject: [PATCH 1/2] feat: use `ArrayPool.Rent` to prevent `byte[]` allocation --- QRCoder/PngByteQRCode.cs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/QRCoder/PngByteQRCode.cs b/QRCoder/PngByteQRCode.cs index 43045b1f..c2dad562 100644 --- a/QRCoder/PngByteQRCode.cs +++ b/QRCoder/PngByteQRCode.cs @@ -1,3 +1,6 @@ +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 +using System.Buffers; +#endif using System; using System.IO; using System.IO.Compression; @@ -36,7 +39,11 @@ public byte[] GetGraphic(int pixelsPerModule, bool drawQuietZones = true) using var png = new PngBuilder(); var size = (QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; png.WriteHeader(size, size, 1, PngBuilder.ColorType.Greyscale); - png.WriteScanlines(DrawScanlines(pixelsPerModule, drawQuietZones)); + var scanLines = DrawScanlines(pixelsPerModule, drawQuietZones); + png.WriteScanlines(scanLines); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 + ArrayPool.Shared.Return(scanLines.Array!); +#endif png.WriteEnd(); return png.GetBytes(); } @@ -68,7 +75,11 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] light var size = (QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; png.WriteHeader(size, size, 1, PngBuilder.ColorType.Indexed); png.WritePalette(darkColorRgba, lightColorRgba); - png.WriteScanlines(DrawScanlines(pixelsPerModule, drawQuietZones)); + var scanLines = DrawScanlines(pixelsPerModule, drawQuietZones); + png.WriteScanlines(scanLines); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 + ArrayPool.Shared.Return(scanLines.Array!); +#endif png.WriteEnd(); return png.GetBytes(); } @@ -79,13 +90,19 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] light /// The number of pixels each dark/light module of the QR code will occupy in the final QR code image. /// Indicates if quiet zones around the QR code should be drawn. /// Returns the bitmap as a byte array. - private byte[] DrawScanlines(int pixelsPerModule, bool drawQuietZones) + private ArraySegment DrawScanlines(int pixelsPerModule, bool drawQuietZones) { var moduleMatrix = QrCodeData.ModuleMatrix; var matrixSize = moduleMatrix.Count - (drawQuietZones ? 0 : 8); var quietZoneOffset = (drawQuietZones ? 0 : 4); var bytesPerScanline = (matrixSize * pixelsPerModule + 7) / 8 + 1; // A monochrome scanline is one byte for filter type then one bit per pixel. - var scanlines = new byte[bytesPerScanline * matrixSize * pixelsPerModule]; + var scanLinesLength = bytesPerScanline * matrixSize * pixelsPerModule; + #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 + var scanlines = ArrayPool.Shared.Rent(scanLinesLength); + Array.Clear(scanlines, 0, scanLinesLength); + #else + var scanlines = new byte[scanLinesLength]; + #endif for (var y = 0; y < matrixSize; y++) { @@ -115,7 +132,7 @@ private byte[] DrawScanlines(int pixelsPerModule, bool drawQuietZones) } } - return scanlines; + return new ArraySegment(scanlines, 0, scanLinesLength); } /// @@ -249,7 +266,7 @@ public void WritePalette(params byte[][] rgbaColors) /// /// Writes the IDAT chunk with the actual picture. /// - public void WriteScanlines(byte[] scanlines) + public void WriteScanlines(ArraySegment scanlines) { using var idatStream = new MemoryStream(); Deflate(idatStream, scanlines); @@ -268,7 +285,7 @@ public void WriteScanlines(byte[] scanlines) idatStream.CopyTo(_stream); #endif // Deflate checksum. - var adler = Adler32(scanlines, 0, scanlines.Length); + var adler = Adler32(scanlines.Array!, 0, scanlines.Count); WriteIntBigEndian(adler); WriteChunkEnd(); @@ -304,10 +321,10 @@ private void WriteIntBigEndian(uint value) _stream.WriteByte((byte)value); } - private static void Deflate(Stream output, byte[] bytes) + private static void Deflate(Stream output, ArraySegment bytes) { using var deflateStream = new DeflateStream(output, CompressionMode.Compress, leaveOpen: true); - deflateStream.Write(bytes, 0, bytes.Length); + deflateStream.Write(bytes.Array!, 0, bytes.Count); } // Reference implementation from RFC 1950. Not optimized. From 4af1e23c5a7aecbb3eb44da9b29d76eeb363a97c Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Wed, 1 Oct 2025 17:02:15 +0100 Subject: [PATCH 2/2] chore: fix whitespace --- QRCoder/PngByteQRCode.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/QRCoder/PngByteQRCode.cs b/QRCoder/PngByteQRCode.cs index c2dad562..761fe8e4 100644 --- a/QRCoder/PngByteQRCode.cs +++ b/QRCoder/PngByteQRCode.cs @@ -97,12 +97,12 @@ private ArraySegment DrawScanlines(int pixelsPerModule, bool drawQuietZone var quietZoneOffset = (drawQuietZones ? 0 : 4); var bytesPerScanline = (matrixSize * pixelsPerModule + 7) / 8 + 1; // A monochrome scanline is one byte for filter type then one bit per pixel. var scanLinesLength = bytesPerScanline * matrixSize * pixelsPerModule; - #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1 var scanlines = ArrayPool.Shared.Rent(scanLinesLength); Array.Clear(scanlines, 0, scanLinesLength); - #else +#else var scanlines = new byte[scanLinesLength]; - #endif +#endif for (var y = 0; y < matrixSize; y++) {