From 05a1e530407c95861613f3e59b59145821fbadbd Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Tue, 23 Nov 2021 22:46:59 +0100 Subject: [PATCH 1/2] Fixed icon-border/-background rendering --- QRCoder/QRCode.cs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/QRCoder/QRCode.cs b/QRCoder/QRCode.cs index c2dbb94f..6cb08915 100644 --- a/QRCoder/QRCode.cs +++ b/QRCoder/QRCode.cs @@ -58,7 +58,7 @@ public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, return bmp; } - public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Bitmap icon=null, int iconSizePercent=15, int iconBorderWidth = 6, bool drawQuietZones = true) + public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Bitmap icon=null, int iconSizePercent=15, int iconBorderWidth = 0, bool drawQuietZones = true, Color? iconBackgroundColor = null) { var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; var offset = drawQuietZones ? 0 : 4 * pixelsPerModule; @@ -72,36 +72,34 @@ public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, gfx.InterpolationMode = InterpolationMode.HighQualityBicubic; gfx.CompositingQuality = CompositingQuality.HighQuality; gfx.Clear(lightColor); - var drawIconFlag = icon != null && iconSizePercent > 0 && iconSizePercent <= 100; - - GraphicsPath iconPath = null; - float iconDestWidth = 0, iconDestHeight = 0, iconX = 0, iconY = 0; - - if (drawIconFlag) - { - iconDestWidth = iconSizePercent * bmp.Width / 100f; - iconDestHeight = drawIconFlag ? iconDestWidth * icon.Height / icon.Width : 0; - iconX = (bmp.Width - iconDestWidth) / 2; - iconY = (bmp.Height - iconDestHeight) / 2; - - var centerDest = new RectangleF(iconX - iconBorderWidth, iconY - iconBorderWidth, iconDestWidth + iconBorderWidth * 2, iconDestHeight + iconBorderWidth * 2); - iconPath = this.CreateRoundedRectanglePath(centerDest, iconBorderWidth * 2); - } - + for (var x = 0; x < size + offset; x = x + pixelsPerModule) { for (var y = 0; y < size + offset; y = y + pixelsPerModule) { var moduleBrush = this.QrCodeData.ModuleMatrix[(y + pixelsPerModule) / pixelsPerModule - 1][(x + pixelsPerModule) / pixelsPerModule - 1] ? darkBrush : lightBrush; - gfx.FillRectangle(moduleBrush , new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule)); } } if (drawIconFlag) { + float iconDestWidth = iconSizePercent * bmp.Width / 100f; + float iconDestHeight = drawIconFlag ? iconDestWidth * icon.Height / icon.Width : 0; + float iconX = (bmp.Width - iconDestWidth) / 2; + float iconY = (bmp.Height - iconDestHeight) / 2; + var centerDest = new RectangleF(iconX - iconBorderWidth, iconY - iconBorderWidth, iconDestWidth + iconBorderWidth * 2, iconDestHeight + iconBorderWidth * 2); var iconDestRect = new RectangleF(iconX, iconY, iconDestWidth, iconDestHeight); + var iconBgBrush = iconBackgroundColor != null ? new SolidBrush((Color)iconBackgroundColor) : lightBrush; + //Only render icon/logo background, if iconBorderWith is set > 0 + if (iconBorderWidth > 0) + { + using (GraphicsPath iconPath = CreateRoundedRectanglePath(centerDest, iconBorderWidth * 2)) + { + gfx.FillPath(iconBgBrush, iconPath); + } + } gfx.DrawImage(icon, iconDestRect, new RectangleF(0, 0, icon.Width, icon.Height), GraphicsUnit.Pixel); } @@ -129,7 +127,7 @@ internal GraphicsPath CreateRoundedRectanglePath(RectangleF rect, int cornerRadi public static class QRCodeHelper { - public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap icon = null, int iconSizePercent = 15, int iconBorderWidth = 6, bool drawQuietZones = true) + public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap icon = null, int iconSizePercent = 15, int iconBorderWidth = 0, bool drawQuietZones = true) { using (var qrGenerator = new QRCodeGenerator()) using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) From 940798b8058a9119f4ead05699545f1419d95a1a Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Tue, 23 Nov 2021 22:47:14 +0100 Subject: [PATCH 2/2] Updated and added new testcases for QRCodeRenderer --- QRCoderTests/QRCodeRendererTests.cs | 128 ++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 24 deletions(-) diff --git a/QRCoderTests/QRCodeRendererTests.cs b/QRCoderTests/QRCodeRendererTests.cs index 44b5ac3c..7dd84513 100644 --- a/QRCoderTests/QRCodeRendererTests.cs +++ b/QRCoderTests/QRCodeRendererTests.cs @@ -19,7 +19,7 @@ public class QRCodeRendererTests #if !NETCOREAPP1_1 [Fact] [Category("QRRenderer/QRCode")] - public void can_create_standard_qrcode_graphic() + public void can_create_qrcode_standard_graphic() { var gen = new QRCodeGenerator(); var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); @@ -28,10 +28,36 @@ public void can_create_standard_qrcode_graphic() var result = HelperFunctions.BitmapToHash(bmp); result.ShouldBe("e8c61b8f0455924fe08ba68686d0d296"); } -#endif + [Fact] + [Category("QRRenderer/QRCode")] + public void can_create_qrcode_standard_graphic_hex() + { + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var bmp = new QRCode(data).GetGraphic(10, "#000000", "#ffffff"); + + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("e8c61b8f0455924fe08ba68686d0d296"); + } + + + [Fact] + [Category("QRRenderer/QRCode")] + public void can_create_qrcode_standard_graphic_without_quietzones() + { + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var bmp = new QRCode(data).GetGraphic(5, Color.Black, Color.White, false); + + var result = HelperFunctions.BitmapToHash(bmp); +#if NET35_OR_GREATER || NET40_OR_GREATER + result.ShouldBe("329e1664f57cbe7332d8d4db04c1d480"); +#else + result.ShouldBe("d703e54a0ba541c6ea69e3d316e394e7"); +#endif + } -#if !NETCOREAPP1_1 && !NETCOREAPP2_0 [Fact] [Category("QRRenderer/QRCode")] @@ -43,7 +69,6 @@ public void can_create_qrcode_with_transparent_logo_graphic() var bmp = new QRCode(data).GetGraphic(10, Color.Black, Color.Transparent, icon: (Bitmap)Image.FromFile(HelperFunctions.GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png")); //Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346 - var result = HelperFunctions.BitmapToHash(bmp); #if NET35_OR_GREATER || NET40_OR_GREATER result.ShouldBe("ee65d96c3013f6032b561cc768251eef"); @@ -70,28 +95,83 @@ public void can_create_qrcode_with_non_transparent_logo_graphic() #endif } - /* - private static byte[] PixelsToAveragedByteArray(Bitmap bmp) + [Fact] + [Category("QRRenderer/QRCode")] + public void can_create_qrcode_with_logo_and_with_transparent_border() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + + var logo = (Bitmap)Image.FromFile(HelperFunctions.GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png"); + var bmp = new QRCode(data).GetGraphic(10, Color.Black, Color.Transparent, icon: logo, iconBorderWidth: 6); + //Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346 + var result = HelperFunctions.BitmapToHash(bmp); +#if NET35_OR_GREATER || NET40_OR_GREATER + result.ShouldBe("ee65d96c3013f6032b561cc768251eef"); +#else + result.ShouldBe("150f8fc7dae4487ba2887d2b2bea1c25"); +#endif + } + + [Fact] + [Category("QRRenderer/QRCode")] + public void can_create_qrcode_with_logo_and_with_standard_border() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + + var logo = (Bitmap)Image.FromFile(HelperFunctions.GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png"); + var bmp = new QRCode(data).GetGraphic(10, Color.Black, Color.White, icon: logo, iconBorderWidth: 6); + //Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346 + var result = HelperFunctions.BitmapToHash(bmp); +#if NET35_OR_GREATER || NET40_OR_GREATER + result.ShouldBe("52207bd86ca5a532fb2095dbaa0ae04c"); +#else + result.ShouldBe("1c926ea1d48f42fdf8e6f1438b774cdd"); +#endif + } + + [Fact] + [Category("QRRenderer/QRCode")] + public void can_create_qrcode_with_logo_and_with_custom_border() { - //Re-color - var bmpTmp = new Bitmap(bmp.Width, bmp.Height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed); - using (var gr = Graphics.FromImage(bmp)) - gr.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height)); - - //Downscale - var bmpSmall = new Bitmap(bmpTmp, new Size(16, 16)); - - var bytes = new System.Collections.Generic.List(); - for (int x = 0; x < bmpSmall.Width; x++) - { - for (int y = 0; y < bmpSmall.Height; y++) - { - bytes.AddRange(new byte[] { bmpSmall.GetPixel(x, y).R, bmpSmall.GetPixel(x, y).G, bmpSmall.GetPixel(x, y).B }); - } - } - return bytes.ToArray(); + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + + var logo = (Bitmap)Image.FromFile(HelperFunctions.GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png"); + var bmp = new QRCode(data).GetGraphic(10, Color.Black, Color.Transparent, icon: logo, iconBorderWidth: 6, iconBackgroundColor: Color.DarkGreen); + //Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346 + var result = HelperFunctions.BitmapToHash(bmp); +#if NET35_OR_GREATER || NET40_OR_GREATER + result.ShouldBe("d2f20d34a973d92b9c3e05db1393b331"); +#else + result.ShouldBe("9a06bfbb72df999b6290b5af5c4037cb"); +#endif + } + + + [Fact] + [Category("QRRenderer/QRCode")] + public void can_instantate_qrcode_parameterless() + { + var svgCode = new QRCode(); + svgCode.ShouldNotBeNull(); + svgCode.ShouldBeOfType(); + } + + [Fact] + [Category("QRRenderer/QRCode")] + public void can_render_qrcode_from_helper() + { + //Create QR code + var bmp = QRCodeHelper.GetQRCode("This is a quick test! 123#?", 10, Color.Black, Color.White, QRCodeGenerator.ECCLevel.H); + + var result = HelperFunctions.BitmapToHash(bmp); + result.ShouldBe("e8c61b8f0455924fe08ba68686d0d296"); } - */ #endif } } \ No newline at end of file