diff --git a/QRCoder/BitmapByteQRCode.cs b/QRCoder/BitmapByteQRCode.cs index 529cc5cc..27b3b34f 100644 --- a/QRCoder/BitmapByteQRCode.cs +++ b/QRCoder/BitmapByteQRCode.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using static QRCoder.QRCodeGenerator; namespace QRCoder; @@ -12,6 +10,10 @@ namespace QRCoder; // ReSharper disable once InconsistentNaming public class BitmapByteQRCode : AbstractQRCode, IDisposable { + private static readonly byte[] _bitmapHeaderPart1 = new byte[] { 0x42, 0x4D }; + private static readonly byte[] _bitmapHeaderPart2 = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 }; + private static readonly byte[] _bitmapHeaderPartEnd = new byte[] { 0x01, 0x00, 0x18, 0x00 }; + /// /// Initializes a new instance of the class. /// Constructor without parameters to be used in COM objects connections. @@ -53,54 +55,83 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgb, byte[] lightC { var sideLength = QrCodeData.ModuleMatrix.Count * pixelsPerModule; - var moduleDark = darkColorRgb.Reverse(); - var moduleLight = lightColorRgb.Reverse(); + // Pre-calculate color/module bytes + byte[] moduleDark = new byte[pixelsPerModule * 3]; + byte[] moduleLight = new byte[pixelsPerModule * 3]; + for (int i = 0; i < pixelsPerModule * 3; i += 3) + { + moduleDark[i] = darkColorRgb[2]; + moduleDark[i + 1] = darkColorRgb[1]; + moduleDark[i + 2] = darkColorRgb[0]; + moduleLight[i] = lightColorRgb[2]; + moduleLight[i + 1] = lightColorRgb[1]; + moduleLight[i + 2] = lightColorRgb[0]; + } + + // Pre-calculate padding bytes + var paddingLen = sideLength % 4; + + // Calculate filesize (header + pixel data + padding) + var fileSize = 54 + (3 * (sideLength * sideLength)) + (sideLength * paddingLen); + + // Bitmap container + byte[] bmp = new byte[fileSize]; + int ix = 0; + + // Header part 1 + Array.Copy(_bitmapHeaderPart1, 0, bmp, ix, _bitmapHeaderPart1.Length); + ix += _bitmapHeaderPart1.Length; + + // Filesize + CopyIntAs4ByteToArray(fileSize, ix, bmp); + ix += 4; - var bmp = new List(); + // Header part 2 + Array.Copy(_bitmapHeaderPart2, 0, bmp, ix, _bitmapHeaderPart2.Length); + ix += _bitmapHeaderPart2.Length; - //header - bmp.AddRange(new byte[] { 0x42, 0x4D, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00 }); + // Width + CopyIntAs4ByteToArray(sideLength, ix, bmp); + ix += 4; + // Height + CopyIntAs4ByteToArray(sideLength, ix, bmp); + ix += 4; - //width - bmp.AddRange(IntTo4Byte(sideLength)); - //height - bmp.AddRange(IntTo4Byte(sideLength)); + // Header end + Array.Copy(_bitmapHeaderPartEnd, 0, bmp, ix, _bitmapHeaderPartEnd.Length); + ix += _bitmapHeaderPartEnd.Length; - //header end - bmp.AddRange(new byte[] { 0x01, 0x00, 0x18, 0x00 }); - bmp.AddRange(new byte[24]); + // Add header null-bytes + ix += 24; - //draw qr code + + // Draw qr code for (var x = sideLength - 1; x >= 0; x -= pixelsPerModule) { - for (int pm = 0; pm < pixelsPerModule; pm++) + var modMatrixX = (x + pixelsPerModule) / pixelsPerModule - 1; + + // Write data for first pixel of pixelsPerModule + int posStartFirstPx = ix; + for (var y = 0; y < sideLength; y += pixelsPerModule) { - for (var y = 0; y < sideLength; y += pixelsPerModule) - { - var module = - QrCodeData.ModuleMatrix[(x + pixelsPerModule) / pixelsPerModule - 1][(y + pixelsPerModule) / pixelsPerModule - 1]; - for (int i = 0; i < pixelsPerModule; i++) - { - bmp.AddRange(module ? moduleDark : moduleLight); - } - } - if (sideLength % 4 != 0) - { - for (int i = 0; i < sideLength % 4; i++) - { - bmp.Add(0x00); - } - } + var module = QrCodeData.ModuleMatrix[modMatrixX][(y + pixelsPerModule) / pixelsPerModule - 1]; + Array.Copy(module ? moduleDark : moduleLight, 0, bmp, ix, moduleDark.Length); + ix += moduleDark.Length; } - } + // Add padding (to make line length a multiple of 4) + ix += paddingLen; + int lenFirstPx = ix - posStartFirstPx; - // write filesize in header - var bmpFileSize = IntTo4Byte(bmp.Count); - for (int i = 0; i < bmpFileSize.Length; i++) - { - bmp[2 + i] = bmpFileSize[i]; + // Re-write (copy) first pixel (pixelsPerModule - 1) times + for (int pm = 0; pm < (pixelsPerModule - 1); pm++) + { + // Draw pixels + Array.Copy(bmp, posStartFirstPx, bmp, ix, lenFirstPx); + ix += lenFirstPx; + } } - return bmp.ToArray(); + + return bmp; } @@ -119,22 +150,22 @@ private byte[] HexColorToByteArray(string colorString) return byteColor; } + /// - /// Converts an integer to a 4-byte array. + /// Converts an integer to a 4 bytes and writes them to a byte array at given position /// /// The integer to convert. - /// Returns the integer as a 4-byte array. - private byte[] IntTo4Byte(int inp) + /// Index of destinationArray where the converted bytes are written to + /// Destination byte array that receives the bytes + private void CopyIntAs4ByteToArray(int inp, int destinationIndex, byte[] destinationArray) { - byte[] bytes = new byte[4]; unchecked { - bytes[3] = (byte)(inp >> 24); - bytes[2] = (byte)(inp >> 16); - bytes[1] = (byte)(inp >> 8); - bytes[0] = (byte)(inp); + destinationArray[destinationIndex + 3] = (byte)(inp >> 24); + destinationArray[destinationIndex + 2] = (byte)(inp >> 16); + destinationArray[destinationIndex + 1] = (byte)(inp >> 8); + destinationArray[destinationIndex + 0] = (byte)(inp); } - return bytes; } } diff --git a/QRCoderBenchmarks/BitmapByteQRCode.cs b/QRCoderBenchmarks/BitmapByteQRCode.cs new file mode 100644 index 00000000..446d8b3a --- /dev/null +++ b/QRCoderBenchmarks/BitmapByteQRCode.cs @@ -0,0 +1,50 @@ +using BenchmarkDotNet.Attributes; +using QRCoder; + +namespace QRCoderBenchmarks; + +[MemoryDiagnoser] +public class BitmapByteQRCodeBenchmark +{ + private readonly Dictionary _samples; + + public BitmapByteQRCodeBenchmark() + { + var eccLvl = QRCoder.QRCodeGenerator.ECCLevel.L; + _samples = new Dictionary() + { + { "small", QRCoder.QRCodeGenerator.GenerateQrCode("ABCD", eccLvl) }, + { "medium", QRCoder.QRCodeGenerator.GenerateQrCode("https://github.com/codebude/QRCoder/blob/f89aa90081f369983a9ba114e49cc6ebf0b2a7b1/QRCoder/Framework4.0Methods/Stream4Methods.cs", eccLvl) }, + { "big", QRCoder.QRCodeGenerator.GenerateQrCode( new string('a', 2600), eccLvl) } + }; + } + + + [Benchmark] + public void RenderBitmapByteQRCodeSmall() + { + var qrCode = new BitmapByteQRCode(_samples["small"]); + _ = qrCode.GetGraphic(10); + } + + [Benchmark] + public void RenderBitmapByteQRCodeMedium() + { + var qrCode = new BitmapByteQRCode(_samples["medium"]); + _ = qrCode.GetGraphic(10); + } + + [Benchmark] + public void RenderBitmapByteQRCodeBig() + { + var qrCode = new BitmapByteQRCode(_samples["big"]); + _ = qrCode.GetGraphic(10); + } + + [Benchmark] + public void RenderBitmapByteQRCodeHuge() + { + var qrCode = new BitmapByteQRCode(_samples["big"]); + _ = qrCode.GetGraphic(50); + } +} diff --git a/QRCoderTests/BitmapByteQRCodeRendererTests.cs b/QRCoderTests/BitmapByteQRCodeRendererTests.cs new file mode 100644 index 00000000..f5ff187c --- /dev/null +++ b/QRCoderTests/BitmapByteQRCodeRendererTests.cs @@ -0,0 +1,50 @@ +using QRCoder; +using QRCoderTests.Helpers; +using QRCoderTests.Helpers.XUnitExtenstions; +using Shouldly; +using Xunit; + + +namespace QRCoderTests; + + +public class BitmapByteQRCodeRendererTests +{ + [Fact] + [Category("QRRenderer/BitmapByteQRCode")] + public void can_render_bitmapbyte_qrcode() + { + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var bmp = new BitmapByteQRCode(data).GetGraphic(10); + + var result = HelperFunctions.ByteArrayToHash(bmp); + result.ShouldBe("2d262d074f5c436ad93025150392dd38"); + } + + + [Fact] + [Category("QRRenderer/BitmapByteQRCode")] + public void can_render_bitmapbyte_qrcode_color_bytearray() + { + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var bmp = new BitmapByteQRCode(data).GetGraphic(10, new byte[] { 30, 30, 30 }, new byte[] { 255, 0, 0 }); + + var result = HelperFunctions.ByteArrayToHash(bmp); + result.ShouldBe("1184507c7eb98f9ca76afd04313c41cb"); + } + + [Fact] + [Category("QRRenderer/BitmapByteQRCode")] + public void can_render_bitmapbyte_qrcode_drawing_color() + { + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var bmp = new BitmapByteQRCode(data).GetGraphic(10, "#e3e3e3", "#ffffff"); + + var result = HelperFunctions.ByteArrayToHash(bmp); + result.ShouldBe("40cd208fc46aa726d6e98a2028ffd2b7"); + } + +}