diff --git a/QRCoder/PngByteQRCode.cs b/QRCoder/PngByteQRCode.cs index 80d1766e..239287d7 100644 --- a/QRCoder/PngByteQRCode.cs +++ b/QRCoder/PngByteQRCode.cs @@ -20,13 +20,13 @@ public PngByteQRCode(QRCodeData data) : base(data) /// /// Creates a black & white PNG of the QR code, using 1-bit grayscale. /// - public byte[] GetGraphic(int pixelsPerModule) + public byte[] GetGraphic(int pixelsPerModule, bool drawQuietZones = true) { using (var png = new PngBuilder()) { - var size = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule; + var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; png.WriteHeader(size, size, 1, PngBuilder.ColorType.Greyscale); - png.WriteScanlines(this.DrawScanlines(pixelsPerModule)); + png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones)); png.WriteEnd(); return png.GetBytes(); } @@ -35,14 +35,14 @@ public byte[] GetGraphic(int pixelsPerModule) /// /// Creates 2-color PNG of the QR code, using 1-bit indexed color. Accepts 3-byte RGB colors for normal images and 4-byte RGBA-colors for transparent images. /// - public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba) + public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, bool drawQuietZones = true) { using (var png = new PngBuilder()) { - var size = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule; + var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; png.WriteHeader(size, size, 1, PngBuilder.ColorType.Indexed); png.WritePalette(darkColorRgba, lightColorRgba); - png.WriteScanlines(this.DrawScanlines(pixelsPerModule)); + png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones)); png.WriteEnd(); return png.GetBytes(); } @@ -51,22 +51,23 @@ public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] light /// /// Creates a bitmap where each pixel is represented by a single bit, dark = 0 and light = 1. /// - private byte[] DrawScanlines(int pixelsPerModule) + private byte[] DrawScanlines(int pixelsPerModule, bool drawQuietZones) { var moduleMatrix = this.QrCodeData.ModuleMatrix; - var matrixSize = moduleMatrix.Count; + 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]; for (var y = 0; y < matrixSize; y++) { - var modules = moduleMatrix[y]; + var modules = moduleMatrix[y+quietZoneOffset]; var scanlineOffset = y * pixelsPerModule * bytesPerScanline; // Draw a scanline with the modules from the QR code. for (var x = 0; x < matrixSize; x++) { - if (modules[x]) + if (modules[x + quietZoneOffset]) { continue; } @@ -319,22 +320,22 @@ private static uint Crc32(byte[] data, int index, int length) public static class PngByteQRCodeHelper { - public static byte[] GetQRCode(string plainText, int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1) + public static byte[] GetQRCode(string plainText, int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true) { using (var qrGenerator = new QRCodeGenerator()) using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) using (var qrCode = new PngByteQRCode(qrCodeData)) - return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba); + return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba, drawQuietZones); } - public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size) + public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size, bool drawQuietZones = true) { using (var qrGen = new QRCodeGenerator()) using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) using (var qrPng = new PngByteQRCode(qrCode)) - return qrPng.GetGraphic(size); + return qrPng.GetGraphic(size, drawQuietZones); } } } diff --git a/QRCoderTests/Helpers/HelperFunctions.cs b/QRCoderTests/Helpers/HelperFunctions.cs index c67fce99..c04a0322 100644 --- a/QRCoderTests/Helpers/HelperFunctions.cs +++ b/QRCoderTests/Helpers/HelperFunctions.cs @@ -38,11 +38,16 @@ public static string BitmapToHash(Bitmap bmp) } return ByteArrayToHash(imgBytes); } +#endif public static string ByteArrayToHash(byte[] data) { +#if !NETCOREAPP1_1 var md5 = new MD5CryptoServiceProvider(); var hash = md5.ComputeHash(data); +#else + var hash = new SshNet.Security.Cryptography.MD5().ComputeHash(data); +#endif return BitConverter.ToString(hash).Replace("-", "").ToLower(); } @@ -50,7 +55,5 @@ public static string StringToHash(string data) { return ByteArrayToHash(Encoding.UTF8.GetBytes(data)); } -#endif - } } diff --git a/QRCoderTests/PngByteQRCodeRendererTests.cs b/QRCoderTests/PngByteQRCodeRendererTests.cs new file mode 100644 index 00000000..7be21684 --- /dev/null +++ b/QRCoderTests/PngByteQRCodeRendererTests.cs @@ -0,0 +1,172 @@ +using Xunit; +using QRCoder; +using Shouldly; +using QRCoderTests.Helpers.XUnitExtenstions; +using QRCoderTests.Helpers; +#if !NETCOREAPP1_1 +using System.Drawing; +using System.IO; +#endif + +namespace QRCoderTests +{ + /**************************************************************************************************** + * Note: Test cases compare the outcome visually even if it's slower than a byte-wise compare. + * This is necessary, because the Deflate implementation differs on the different target + * platforms and thus the outcome, even if visually identical, differs. Thus only a visual + * test method makes sense. In addition bytewise differences shouldn't be important, if the + * visual outcome is identical and thus the qr code is identical/scannable. + ****************************************************************************************************/ + public class PngByteQRCodeRendererTests + { + + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_render_pngbyte_qrcode_blackwhite() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5); + +#if NETCOREAPP1_1 + var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); + result.ShouldBe("1fc35c3bea6fad47427143ce716c83b8"); +#else + using (var mStream = new MemoryStream(pngCodeGfx)) + { + var bmp = (Bitmap)Image.FromStream(mStream); + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("18b19e6037cff06ae995d8d487b0e46e"); + } +#endif + } + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_render_pngbyte_qrcode_color() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5, new byte[] { 255, 0, 0 }, new byte[] { 0, 0, 255 }); + +#if NETCOREAPP1_1 + var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); + result.ShouldBe("0144b1d40aa6eeb6cb07df42822ea0a7"); +#else + using (var mStream = new MemoryStream(pngCodeGfx)) + { + var bmp = (Bitmap)Image.FromStream(mStream); + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("37ae73e90b66beac317b790be3db24cc"); + } +#endif + } + + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_render_pngbyte_qrcode_color_with_alpha() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 }); + +#if NETCOREAPP1_1 + var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); + result.ShouldBe("627ce564fb5e17be42e4a85e907a17b5"); +#else + using (var mStream = new MemoryStream(pngCodeGfx)) + { + var bmp = (Bitmap)Image.FromStream(mStream); + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("c56c2a9535fd8e9a92a6ac9709d21e67"); + } +#endif + } + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_render_pngbyte_qrcode_color_without_quietzones() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var pngCodeGfx = new PngByteQRCode(data).GetGraphic(5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 }, false); + +#if NETCOREAPP1_1 + var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); + result.ShouldBe("07f760b3eb54901840b094d31e299713"); +#else + File.WriteAllBytes(@"C:\Temp\pngbyte_35.png", pngCodeGfx); + using (var mStream = new MemoryStream(pngCodeGfx)) + { + var bmp = (Bitmap)Image.FromStream(mStream); + bmp.MakeTransparent(Color.Transparent); + var result = HelperFunctions.BitmapToHash(bmp); +#if NET35_OR_GREATER || NET40_OR_GREATER + result.ShouldBe("75be11d582575617d2490c54b69e844e"); +#else + result.ShouldBe("fbbc8255ebf3e4f4a1d21f0dd15f76f8"); +#endif + } +#endif + } + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_instantate_pngbyte_qrcode_parameterless() + { + var pngCode = new PngByteQRCode(); + pngCode.ShouldNotBeNull(); + pngCode.ShouldBeOfType(); + } + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_render_pngbyte_qrcode_from_helper() + { + //Create QR code + var pngCodeGfx = PngByteQRCodeHelper.GetQRCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L, 10); + +#if NETCOREAPP1_1 + var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); + result.ShouldBe("c562388f4f3cf13a299b469a3e3b852f"); +#else + using (var mStream = new MemoryStream(pngCodeGfx)) + { + var bmp = (Bitmap)Image.FromStream(mStream); + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("1978fb11ce26acf9b6cb7490b4c44ef2"); + } +#endif + } + + [Fact] + [Category("QRRenderer/PngByteQRCode")] + public void can_render_pngbyte_qrcode_from_helper_2() + { + //Create QR code + var pngCodeGfx = PngByteQRCodeHelper.GetQRCode("This is a quick test! 123#?", 5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 }, QRCodeGenerator.ECCLevel.L); + +#if NETCOREAPP1_1 + var result = HelperFunctions.ByteArrayToHash(pngCodeGfx); + result.ShouldBe("627ce564fb5e17be42e4a85e907a17b5"); +#else + using (var mStream = new MemoryStream(pngCodeGfx)) + { + var bmp = (Bitmap)Image.FromStream(mStream); + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("c56c2a9535fd8e9a92a6ac9709d21e67"); + } +#endif + } + + } +} + + + diff --git a/QRCoderTests/QRCoderTests.csproj b/QRCoderTests/QRCoderTests.csproj index b14a2471..ce23de9d 100644 --- a/QRCoderTests/QRCoderTests.csproj +++ b/QRCoderTests/QRCoderTests.csproj @@ -36,6 +36,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive +