diff --git a/QRCoder.ImageSharp/ArtQRCode.cs b/QRCoder.ImageSharp/ArtQRCode.cs
new file mode 100644
index 00000000..74a8b9c2
--- /dev/null
+++ b/QRCoder.ImageSharp/ArtQRCode.cs
@@ -0,0 +1,237 @@
+#if NET5_0 || NET6_0 || NETSTANDARD2_1_OR_GREATER
+
+using System;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Drawing;
+using SixLabors.ImageSharp.Drawing.Processing;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using static QRCoder.ImageSharp.ArtQRCode;
+using static QRCoder.QRCodeGenerator;
+// pull request raised to extend library used.
+namespace QRCoder.ImageSharp
+{
+ public class ArtQRCode : AbstractQRCode, IDisposable
+ {
+ ///
+ /// Constructor without params to be used in COM Objects connections
+ ///
+ public ArtQRCode() { }
+
+ ///
+ /// Creates new ArtQrCode object
+ ///
+ /// QRCodeData generated by the QRCodeGenerator
+ public ArtQRCode(QRCodeData data) : base(data) { }
+
+ ///
+ /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
+ ///
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// QRCode graphic as bitmap
+ public Image GetGraphic(int pixelsPerModule)
+ {
+ return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent);
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
+ ///
+ /// A bitmap object that will be used as background picture
+ /// QRCode graphic as bitmap
+ public Image GetGraphic(Image? backgroundImage = null)
+ {
+ return this.GetGraphic(10, Color.Black, Color.White, Color.Transparent, backgroundImage: backgroundImage);
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules and various user settings
+ ///
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// Color of the dark modules
+ /// Color of the light modules
+ /// Color of the background
+ /// A bitmap object that will be used as background picture
+ /// Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.
+ /// If true a white border is drawn around the whole QR Code
+ /// Style of the quiet zones
+ /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone
+ /// Optional image that should be used instead of the default finder patterns
+ /// QRCode graphic as bitmap
+ public Image GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Image? backgroundImage = null, double pixelSizeFactor = 0.8,
+ bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted,
+ BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Image? finderPatternImage = null)
+ {
+ if (pixelSizeFactor > 1)
+ throw new ArgumentException("The parameter pixelSize must be between 0 and 1. (0-100%)");
+
+ int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule / pixelSizeFactor));
+
+ var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
+ var offset = (drawQuietZones ? 0 : 4);
+ var size = numModules * pixelsPerModule;
+
+ var image = new Image(size, size);
+
+ var options = new DrawingOptions
+ {
+ GraphicsOptions = new GraphicsOptions
+ {
+ Antialias = true,
+ AntialiasSubpixelDepth = 2
+ }
+ };
+
+ IBrush lightBrush = Brushes.Solid(lightColor);
+ IBrush darkBrush = Brushes.Solid(darkColor);
+
+ IBrush backgroundBrush = Brushes.Solid(backgroundColor);
+
+ //background rectangle:
+ IPath backgroundRectangle = new RectangularPolygon(0, 0, size, size);
+
+ image.Mutate(x => x.Fill(options, brush: backgroundBrush, path: backgroundRectangle));
+
+ if(backgroundImage != null)
+ {
+ switch (backgroundImageStyle)
+ {
+ case BackgroundImageStyle.Fill:
+ backgroundImage = backgroundImage.Clone(x => x.Resize(size, size));
+ image.Mutate(x => x.DrawImage(backgroundImage, new Point(0, 0), 1));
+ break;
+ case BackgroundImageStyle.DataAreaOnly:
+ var bgOffset = 4 - offset;
+ backgroundImage = backgroundImage.Clone(x => x.Resize(size - (2 * bgOffset * pixelsPerModule), size - (2 * bgOffset * pixelsPerModule)));
+ image.Mutate(x => x.DrawImage(backgroundImage, new Point(bgOffset * pixelsPerModule, bgOffset * pixelsPerModule), 1));
+ break;
+ }
+ }
+
+ for (var x = 0; x < numModules; x += 1)
+ {
+ for (var y = 0; y < numModules; y += 1)
+ {
+ var rectangleF = new RectangularPolygon(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule);
+ var elipse = new EllipsePolygon(x * pixelsPerModule + pixelSize / 2, y * pixelsPerModule + pixelSize / 2, pixelsPerModule, pixelsPerModule);
+
+ var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x];
+ var solidBrush = pixelIsDark ? darkBrush : lightBrush;
+ //var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;
+
+ if (!IsPartOfFinderPattern(x, y, numModules, offset))
+ if (drawQuietZones && quietZoneRenderingStyle == QuietZoneStyle.Flat && IsPartOfQuietZone(x, y, numModules))
+ image.Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ else
+ image.Mutate(im => im.Fill(options, solidBrush, elipse));
+ else if (finderPatternImage == null)
+ image.Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ }
+ }
+
+ if (finderPatternImage != null)
+ {
+ var finderPatternSize = 7 * pixelsPerModule;
+
+ finderPatternImage = finderPatternImage.Clone(x => x.Resize(finderPatternSize, finderPatternSize));
+
+ image.Mutate(x => x.DrawImage(finderPatternImage, 1)); //default position is 0,0 //new Rectangle(0, 0, finderPatternSize, finderPatternSize)
+ image.Mutate(x => x.DrawImage(finderPatternImage, new Point(size - finderPatternSize, 0), 1));
+ image.Mutate(x => x.DrawImage(finderPatternImage, new Point(0, size - finderPatternSize), 1));
+ //graphics.DrawImage(finderPatternImage, new Rectangle(0, size - finderPatternSize, finderPatternSize, finderPatternSize));
+ }
+ return image;
+ }
+
+ ///
+ /// Checks if a given module(-position) is part of the quietzone of a QR code
+ ///
+ /// X position
+ /// Y position
+ /// Total number of modules per row
+ /// true, if position is part of quiet zone
+ private bool IsPartOfQuietZone(int x, int y, int numModules)
+ {
+ return
+ x < 4 || //left
+ y < 4 || //top
+ x > numModules - 5 || //right
+ y > numModules - 5; //bottom
+ }
+
+
+ ///
+ /// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
+ ///
+ /// X position
+ /// Y position
+ /// Total number of modules per row
+ /// Offset in modules (usually depending on drawQuietZones parameter)
+ /// true, if position is part of any finder pattern
+ private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
+ {
+ var cornerSize = 11 - offset;
+ var outerLimitLow = (numModules - cornerSize - 1);
+ var outerLimitHigh = outerLimitLow + 8;
+ var invertedOffset = 4 - offset;
+ return
+ (x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
+ (x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
+ (x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
+ }
+
+ ///
+ /// Defines how the quiet zones shall be rendered.
+ ///
+ public enum QuietZoneStyle
+ {
+ Dotted,
+ Flat
+ }
+
+ ///
+ /// Defines how the background image (if set) shall be rendered.
+ ///
+ public enum BackgroundImageStyle
+ {
+ Fill,
+ DataAreaOnly
+ }
+ }
+
+ public static class ArtQRCodeHelper
+ {
+ ///
+ /// Helper function to create an ArtQRCode graphic with a single function call
+ ///
+ /// Text/payload to be encoded inside the QR code
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// Color of the dark modules
+ /// Color of the light modules
+ /// Color of the background
+ /// The level of error correction data
+ /// Shall the generator be forced to work in UTF-8 mode?
+ /// Should the byte-order-mark be used?
+ /// Which ECI mode shall be used?
+ /// Set fixed QR code target version.
+ /// A bitmap object that will be used as background picture
+ /// Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.
+ /// If true a white border is drawn around the whole QR Code
+ /// Style of the quiet zones
+ /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone
+ /// Optional image that should be used instead of the default finder patterns
+ /// QRCode graphic as bitmap
+ public static Image GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false,
+ bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Image? backgroundImage = null, double pixelSizeFactor = 0.8,
+ bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat,
+ BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Image? finderPatternImage = null)
+ {
+ using (var qrGenerator = new QRCodeGenerator())
+ using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
+ using (var qrCode = new ArtQRCode(qrCodeData))
+ return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, backgroundImage, pixelSizeFactor, drawQuietZones, quietZoneRenderingStyle, backgroundImageStyle, finderPatternImage);
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/QRCoder.ImageSharp/ImageSharpQRCode.cs b/QRCoder.ImageSharp/ImageSharpQRCode.cs
new file mode 100644
index 00000000..58e5e2cd
--- /dev/null
+++ b/QRCoder.ImageSharp/ImageSharpQRCode.cs
@@ -0,0 +1,203 @@
+using QRCoder.Models;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Drawing;
+using SixLabors.ImageSharp.Drawing.Processing;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace QRCoder.ImageSharp
+{
+
+ public class ImageSharpQRCode : AbstractQRCode, IDisposable
+ {
+ public ImageSharpQRCode()
+ {
+ }
+
+ public ImageSharpQRCode(QRCodeData data) : base(data)
+ {
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
+ ///
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// QRCode graphic as bitmap
+ public Image GetGraphic(int pixelsPerModule, Image? logoImage = null, LogoLocation logoLocation = LogoLocation.BottomRight, LogoBackgroundShape logoBackgroundShape = LogoBackgroundShape.Rectangle)
+ {
+ return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent, logoImage, logoLocation, logoBackgroundShape);
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
+ ///
+ /// A bitmap object that will be used as background picture
+ /// QRCode graphic as bitmap
+ public Image GetGraphic(Image? logoImage = null)
+ {
+ return this.GetGraphic(10, Color.Black, Color.White, Color.Transparent, logoImage);
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules and various user settings
+ ///
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// Color of the dark modules
+ /// Color of the light modules
+ /// Color of the background
+ /// A bitmap object that will be used as background picture
+ /// Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.
+ /// If true a white border is drawn around the whole QR Code
+ /// Style of the quiet zones
+ /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone
+ /// Optional image that should be used instead of the default finder patterns
+ /// QRCode graphic as bitmap
+ public Image GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Image? logoImage = null, LogoLocation logoLocation = LogoLocation.BottomRight, LogoBackgroundShape logoBackgroundShape = LogoBackgroundShape.Circle, double pixelSizeFactor = 0.8,
+ bool drawQuietZones = true)
+ {
+ if (pixelSizeFactor > 1)
+ throw new ArgumentException("The parameter pixelSize must be between 0 and 1. (0-100%)");
+
+ int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule / pixelSizeFactor));
+
+ var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
+ var offset = (drawQuietZones ? 0 : 4);
+ var size = numModules * pixelsPerModule;
+
+ var image = new Image(size, size);
+
+ var options = new DrawingOptions
+ {
+ GraphicsOptions = new GraphicsOptions
+ {
+ Antialias = true,
+ AntialiasSubpixelDepth = 2
+ }
+ };
+
+ IBrush lightBrush = Brushes.Solid(lightColor);
+ IBrush darkBrush = Brushes.Solid(darkColor);
+
+ IBrush backgroundBrush = Brushes.Solid(backgroundColor);
+
+ //background rectangle:
+ IPath backgroundRectangle = new RectangularPolygon(0, 0, size, size);
+
+ image.Mutate(x => x.Fill(options, brush: backgroundBrush, path: backgroundRectangle));
+
+ //if (backgroundImage != null)
+ //{
+ // switch (backgroundImageStyle)
+ // {
+ // case BackgroundImageStyle.Fill:
+ // backgroundImage = backgroundImage.Clone(x => x.Resize(size, size));
+ // image.Mutate(x => x.DrawImage(backgroundImage, new Point(0, 0), 1));
+ // break;
+ // case BackgroundImageStyle.DataAreaOnly:
+ // var bgOffset = 4 - offset;
+ // backgroundImage = backgroundImage.Clone(x => x.Resize(size - (2 * bgOffset * pixelsPerModule), size - (2 * bgOffset * pixelsPerModule)));
+ // image.Mutate(x => x.DrawImage(backgroundImage, new Point(bgOffset * pixelsPerModule, bgOffset * pixelsPerModule), 1));
+ // break;
+ // }
+ //}
+
+ for (var x = 0; x < numModules; x += 1)
+ {
+ for (var y = 0; y < numModules; y += 1)
+ {
+ var rectangleF = new RectangularPolygon(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule);
+
+ var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x];
+ var solidBrush = pixelIsDark ? darkBrush : lightBrush;
+ //var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;
+
+ if (!IsPartOfFinderPattern(x, y, numModules, offset))
+ if (drawQuietZones && IsPartOfQuietZone(x, y, numModules))
+ image.Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ else
+ image.Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ else
+ image.Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ }
+ }
+
+ if(logoImage != null)
+ {
+ var logoSize = (int)(size * 0.15);
+ var logoOffset = drawQuietZones ? 4 : 0;
+ var locationPadding = logoLocation switch
+ {
+ LogoLocation.Center => new PointF(size / 2, size / 2),
+ _ => new Point(size - logoSize / 2 - logoOffset * pixelsPerModule, size - logoSize / 2 - logoOffset * pixelsPerModule),
+ };
+
+
+ var locationLogo = logoLocation switch
+ {
+ LogoLocation.Center => new Point(size / 2 - logoSize / 2, size / 2 - logoSize / 2),
+ _ => new Point(size - logoSize - logoOffset * pixelsPerModule, size - logoSize - logoOffset * pixelsPerModule),
+ };
+
+
+ IPath backgroundShape = logoBackgroundShape switch
+ {
+ LogoBackgroundShape.Circle => new EllipsePolygon(locationPadding, logoSize / 2),
+ _ => logoLocation == LogoLocation.Center ? new RectangularPolygon(size / 2 - logoSize/2, size /2 - logoSize /2, logoSize, logoSize) : new RectangularPolygon(size - logoSize - logoOffset * pixelsPerModule, size - logoSize - logoOffset * pixelsPerModule, logoSize, logoSize),
+ };
+
+ backgroundShape = backgroundShape.Scale(1.03f);
+
+ image.Mutate(x => x.Fill(backgroundBrush, backgroundShape));
+
+ logoImage = logoImage.Clone(x =>
+ {
+ x.Resize(logoSize, logoSize);
+
+ });
+ image.Mutate(x => x.DrawImage(logoImage, locationLogo, 1));
+ }
+
+ return image;
+ }
+
+ ///
+ /// Checks if a given module(-position) is part of the quietzone of a QR code
+ ///
+ /// X position
+ /// Y position
+ /// Total number of modules per row
+ /// true, if position is part of quiet zone
+ private bool IsPartOfQuietZone(int x, int y, int numModules)
+ {
+ return
+ x < 4 || //left
+ y < 4 || //top
+ x > numModules - 5 || //right
+ y > numModules - 5; //bottom
+ }
+
+
+ ///
+ /// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
+ ///
+ /// X position
+ /// Y position
+ /// Total number of modules per row
+ /// Offset in modules (usually depending on drawQuietZones parameter)
+ /// true, if position is part of any finder pattern
+ private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
+ {
+ var cornerSize = 11 - offset;
+ var outerLimitLow = (numModules - cornerSize - 1);
+ var outerLimitHigh = outerLimitLow + 8;
+ var invertedOffset = 4 - offset;
+ return
+ (x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
+ (x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
+ (x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
+ }
+ }
+}
diff --git a/QRCoder.ImageSharp/QRCoder.ImageSharp.csproj b/QRCoder.ImageSharp/QRCoder.ImageSharp.csproj
new file mode 100644
index 00000000..3f179b4c
--- /dev/null
+++ b/QRCoder.ImageSharp/QRCoder.ImageSharp.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netstandard2.1;net5.0;net5.0-windows;net6.0;net6.0-windows
+ disable
+ enable
+ 1.0.1-dev
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QRCoder.ImageSharpTests/ArtQRCodeRendererTests.cs b/QRCoder.ImageSharpTests/ArtQRCodeRendererTests.cs
new file mode 100644
index 00000000..20d2fd63
--- /dev/null
+++ b/QRCoder.ImageSharpTests/ArtQRCodeRendererTests.cs
@@ -0,0 +1,115 @@
+
+#if !NET35 && NET6_0
+using QRCoder;
+using QRCoderTests.Helpers;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+using FluentAssertions;
+#if NET6_0
+using QRCoder.ImageSharp;
+#endif
+
+namespace QRCoderTests
+{
+ [TestClass]
+ public class ImageSharpArtQRCodeRendererTests
+ {
+
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void can_create_standard_qrcode_graphic()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var bmp = new ArtQRCode(data).GetGraphic(10);
+ var result = HelperFunctions.ImageToHash(bmp);
+ //bmp.SaveAsPng("qrcode_imagesharp.png");
+ result.Should().Be("e8de533db63b5784de075c4e4cc3e0c9"); //different hash than the System.Drawing example since the algorithm is slighty different -> (anti-aliasing).
+ }
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void can_create_standard_qrcode_graphic_with_custom_finder()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var finder = new Image(15, 15);
+ var bmp = new ArtQRCode(data).GetGraphic(10, Color.Black, Color.White, Color.Transparent, finderPatternImage: finder);
+ //bmp.SaveAsPng("finder_custom.png");
+ var result = HelperFunctions.ImageToHash(bmp);
+
+ result.Should().Be("63dcb31fd8910a10aba57929b6327790");
+
+ }
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void can_create_standard_qrcode_graphic_without_quietzone()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var bmp = new ArtQRCode(data).GetGraphic(10, Color.Black, Color.White, Color.Transparent, drawQuietZones: false);
+
+ var result = HelperFunctions.ImageToHash(bmp);
+
+ //bmp.SaveAsPng("without_quietzone.png");
+
+ result.Should().Be("d96f9c8c64cdb5c651dd59bab6b564d7");
+
+ }
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void can_create_standard_qrcode_graphic_with_background()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var bmp = new ArtQRCode(data).GetGraphic(Image.Load(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.ImageToHash(bmp);
+
+ bmp.SaveAsPng("custom_background.png");
+
+ result.Should().Be("aea31c69506b0d933fd49205e7b37f33");
+
+ }
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void should_throw_pixelfactor_oor_exception()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var aCode = new ArtQRCode(data);
+
+ var exception = Assert.ThrowsException(() => aCode.GetGraphic(10, Color.Black, Color.White, Color.Transparent, pixelSizeFactor: 2));
+
+ exception.Message.Should().Be("The parameter pixelSize must be between 0 and 1. (0-100%)");
+ }
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void can_instantate_parameterless()
+ {
+ var asciiCode = new ArtQRCode();
+ asciiCode.Should().NotBeNull();
+ asciiCode.Should().BeOfType();
+ }
+
+ [TestMethod]
+ [TestCategory("QRRenderer/ArtQRCode")]
+ public void can_render_artqrcode_from_helper()
+ {
+ //Create QR code
+ var bmp = ArtQRCodeHelper.GetQRCode("A", 10, Color.Black, Color.White, Color.Transparent, QRCodeGenerator.ECCLevel.L);
+
+ var result = HelperFunctions.ImageToHash(bmp);
+ //bmp.SaveAsPng("Helper.png");
+ result.Should().Be("1dbda9e61f832a7ebdb5d97f8c6e8fb6");
+
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/QRCoder.ImageSharpTests/Helpers/HelperFunctions.cs b/QRCoder.ImageSharpTests/Helpers/HelperFunctions.cs
new file mode 100644
index 00000000..e46e1ddd
--- /dev/null
+++ b/QRCoder.ImageSharpTests/Helpers/HelperFunctions.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Text;
+using System.IO;
+using System.Security.Cryptography;
+#if NET6_0_OR_GREATER
+using SixLabors.ImageSharp.Formats.Png;
+#endif
+
+#if !NETCOREAPP1_1
+using System.Drawing;
+#endif
+#if NETFRAMEWORK || NET5_0_WINDOWS
+using SW = System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+#endif
+
+
+namespace QRCoderTests.Helpers
+{
+ public static class HelperFunctions
+ {
+
+#if NETFRAMEWORK || NET5_0_WINDOWS
+ public static BitmapSource ToBitmapSource(DrawingImage source)
+ {
+ DrawingVisual drawingVisual = new DrawingVisual();
+ DrawingContext drawingContext = drawingVisual.RenderOpen();
+ drawingContext.DrawImage(source, new SW.Rect(new SW.Point(0, 0), new SW.Size(source.Width, source.Height)));
+ drawingContext.Close();
+
+ RenderTargetBitmap bmp = new RenderTargetBitmap((int)source.Width, (int)source.Height, 96, 96, PixelFormats.Pbgra32);
+ bmp.Render(drawingVisual);
+ return bmp;
+ }
+
+ public static Bitmap BitmapSourceToBitmap(DrawingImage xamlImg)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ PngBitmapEncoder encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(ToBitmapSource(xamlImg)));
+ encoder.Save(ms);
+
+ using (Bitmap bmp = new Bitmap(ms))
+ {
+ return new Bitmap(bmp);
+ }
+ }
+ }
+#endif
+
+#if !NETCOREAPP1_1
+ public static string GetAssemblyPath()
+ {
+ return
+#if NET5_0 || NET6_0
+ AppDomain.CurrentDomain.BaseDirectory;
+#elif NET35 || NET452
+ Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Replace("file:\\", "");
+#else
+ Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location).Replace("file:\\", "");
+#endif
+ }
+#endif
+
+
+#if NET6_0
+ public static string ImageToHash(SixLabors.ImageSharp.Image image)
+ {
+ using var ms = new MemoryStream();
+ image.Save(ms, new PngEncoder());
+ byte[] imgBytes = ms.ToArray();
+ return ByteArrayToHash(imgBytes);
+ }
+#endif
+
+ public static string ByteArrayToHash(byte[] data)
+ {
+#if !NETCOREAPP1_1
+ var md5 = MD5.Create();
+ var hash = md5.ComputeHash(data);
+#else
+ var hash = new SshNet.Security.Cryptography.MD5().ComputeHash(data);
+#endif
+ return BitConverter.ToString(hash).Replace("-", "").ToLower();
+ }
+
+ public static string StringToHash(string data)
+ {
+ return ByteArrayToHash(Encoding.UTF8.GetBytes(data));
+ }
+ }
+}
diff --git a/QRCoder.ImageSharpTests/ImageSharpQrCodeTests.cs b/QRCoder.ImageSharpTests/ImageSharpQrCodeTests.cs
new file mode 100644
index 00000000..77f112fb
--- /dev/null
+++ b/QRCoder.ImageSharpTests/ImageSharpQrCodeTests.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using QRCoder.ImageSharp;
+using QRCoder.Models;
+using QRCoderTests.Helpers;
+using SixLabors.ImageSharp;
+
+namespace QRCoder.ImageSharpTests
+{
+ [TestClass]
+ public class ImageSharpQrCodeTests
+ {
+ [TestMethod]
+ public void TestRegularQrCode()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var qrcode = new ImageSharpQRCode(data);
+ var image = qrcode.GetGraphic();
+ image.SaveAsPng("imageSharpQr.png");
+ }
+
+ [TestMethod]
+ public void TestLogoQrCode()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var qrcode = new ImageSharpQRCode(data);
+ var image = qrcode.GetGraphic(60, Image.Load(HelperFunctions.GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png"), LogoLocation.Center, LogoBackgroundShape.Circle);
+ image.SaveAsPng("imageSharpQr_logo.png");
+ }
+ }
+}
diff --git a/QRCoder.ImageSharpTests/QRCoder.ImageSharpTests.csproj b/QRCoder.ImageSharpTests/QRCoder.ImageSharpTests.csproj
new file mode 100644
index 00000000..54dcc637
--- /dev/null
+++ b/QRCoder.ImageSharpTests/QRCoder.ImageSharpTests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/QRCoder.ImageSharpTests/Usings.cs b/QRCoder.ImageSharpTests/Usings.cs
new file mode 100644
index 00000000..ab67c7ea
--- /dev/null
+++ b/QRCoder.ImageSharpTests/Usings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file
diff --git a/QRCoder.ImageSharpTests/assets/noun_Scientist_2909361.svg b/QRCoder.ImageSharpTests/assets/noun_Scientist_2909361.svg
new file mode 100644
index 00000000..869a0e73
--- /dev/null
+++ b/QRCoder.ImageSharpTests/assets/noun_Scientist_2909361.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/QRCoder.ImageSharpTests/assets/noun_software engineer_2909346.png b/QRCoder.ImageSharpTests/assets/noun_software engineer_2909346.png
new file mode 100644
index 00000000..bc100986
Binary files /dev/null and b/QRCoder.ImageSharpTests/assets/noun_software engineer_2909346.png differ
diff --git a/QRCoder.SkiaSharp/Extensions/SaveExtensions.cs b/QRCoder.SkiaSharp/Extensions/SaveExtensions.cs
new file mode 100644
index 00000000..5e1631c0
--- /dev/null
+++ b/QRCoder.SkiaSharp/Extensions/SaveExtensions.cs
@@ -0,0 +1,36 @@
+using QRCoder.Models;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QRCoder.SkiaSharp.Extensions
+{
+ public static class SaveExtensions
+ {
+ ///
+ /// Saves a png of the qr code
+ ///
+ ///
+ ///
+ public static void SaveToPng(this SkiaSharpQRCode skiaSharpQRCode, string path, int quality = 80)
+ {
+ using var image = skiaSharpQRCode.GetGraphic();
+ using var imageData = image.Encode(SKEncodedImageFormat.Png, quality);
+ using var stream = File.OpenWrite(path);
+ imageData.SaveTo(stream);
+ }
+
+ public static void SaveToPng(this SkiaSharpQRCode skiaSharpQRCode, Stream stream, int quality, int pixelsPerModule, SKImage? logoImage = null, LogoLocation logoLocation = LogoLocation.BottomRight, LogoBackgroundShape logoBackgroundShape = LogoBackgroundShape.Circle) => SaveToPng(skiaSharpQRCode, stream, quality, pixelsPerModule, SKColors.Black, SKColors.White, SKColors.White, logoImage, logoLocation, logoBackgroundShape);
+
+ public static void SaveToPng(this SkiaSharpQRCode skiaSharpQRCode, Stream stream, int quality, int pixelsPerModule, SKColor darkColor, SKColor lightColor, SKColor backgroundColor, SKImage? logoImage = null, LogoLocation logoLocation = LogoLocation.BottomRight, LogoBackgroundShape logoBackgroundShape = LogoBackgroundShape.Circle, bool drawQuietZones = true)
+ {
+ using var image = skiaSharpQRCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, logoImage, logoLocation, logoBackgroundShape, drawQuietZones: drawQuietZones);
+ using var imageData = image.Encode(SKEncodedImageFormat.Png, quality);
+ imageData.SaveTo(stream);
+ stream.Position = 0;
+ }
+ }
+}
diff --git a/QRCoder.SkiaSharp/QRCoder.SkiaSharp.csproj b/QRCoder.SkiaSharp/QRCoder.SkiaSharp.csproj
new file mode 100644
index 00000000..5b45031f
--- /dev/null
+++ b/QRCoder.SkiaSharp/QRCoder.SkiaSharp.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net6.0
+ enable
+ enable
+ 1.0.0-dev1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/QRCoder.SkiaSharp/SkiaSharpQRCode.cs b/QRCoder.SkiaSharp/SkiaSharpQRCode.cs
new file mode 100644
index 00000000..500bfbd5
--- /dev/null
+++ b/QRCoder.SkiaSharp/SkiaSharpQRCode.cs
@@ -0,0 +1,222 @@
+using QRCoder.Models;
+using SkiaSharp;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace QRCoder.SkiaSharp
+{
+ public class SkiaSharpQRCode : AbstractQRCode, IDisposable
+ {
+ public SkiaSharpQRCode()
+ {
+ }
+
+ public SkiaSharpQRCode(QRCodeData data) : base(data)
+ {
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
+ ///
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// QRCode graphic as bitmap
+ public SKImage GetGraphic(int pixelsPerModule, SKImage? logoImage = null, LogoLocation logoLocation = LogoLocation.BottomRight, LogoBackgroundShape logoBackgroundShape = LogoBackgroundShape.Rectangle)
+ {
+ return this.GetGraphic(pixelsPerModule, SKColors.Black, SKColors.White, SKColors.Transparent, logoImage, logoLocation, logoBackgroundShape);
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
+ ///
+ /// A bitmap object that will be used as background picture
+ /// QRCode graphic as bitmap
+ public SKImage GetGraphic(SKImage? logoImage = null)
+ {
+ return this.GetGraphic(10, SKColors.Black, SKColors.White, SKColors.Transparent, logoImage);
+ }
+
+ ///
+ /// Renders an art-style QR code with dots as modules and various user settings
+ ///
+ /// Amount of px each dark/light module of the QR code shall take place in the final QR code image
+ /// Color of the dark modules
+ /// Color of the light modules
+ /// Color of the background
+ /// A bitmap object that will be used as background picture
+ /// Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be.
+ /// If true a white border is drawn around the whole QR Code
+ /// Style of the quiet zones
+ /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone
+ /// Optional image that should be used instead of the default finder patterns
+ /// QRCode graphic as bitmap
+ public SKImage GetGraphic(int pixelsPerModule, SKColor darkColor, SKColor lightColor, SKColor backgroundColor, SKImage? logoImage = null, LogoLocation logoLocation = LogoLocation.BottomRight, LogoBackgroundShape logoBackgroundShape = LogoBackgroundShape.Circle, double pixelSizeFactor = 0.8,
+ bool drawQuietZones = true)
+ {
+ if (pixelSizeFactor > 1)
+ throw new ArgumentException("The parameter pixelSize must be between 0 and 1. (0-100%)");
+
+ int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule / pixelSizeFactor));
+
+ var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
+ var offset = (drawQuietZones ? 0 : 4);
+ var size = numModules * pixelsPerModule;
+
+ var imageInfo = new SKImageInfo(size, size, SKColorType.RgbaF32);
+
+ using var surface = SKSurface.Create(imageInfo);
+
+ var canvas = surface.Canvas;
+
+ var lightBrush = new SKPaint
+ {
+ Color = lightColor,
+ IsAntialias = true,
+ Style = SKPaintStyle.Fill
+ };
+
+ var darkBrush = new SKPaint
+ {
+ Color = darkColor,
+ IsAntialias = true,
+ Style = SKPaintStyle.Fill
+ };
+
+ var backgroundBrush = new SKPaint
+ {
+ Color = backgroundColor,
+ IsAntialias = true,
+ Style = SKPaintStyle.Fill
+ };
+
+
+ //background rectangle:
+
+ canvas.DrawRect(0, 0, size, size, backgroundBrush);
+
+ //if (backgroundImage != null)
+ //{
+ // switch (backgroundImageStyle)
+ // {
+ // case BackgroundImageStyle.Fill:
+ // backgroundImage = backgroundImage.Clone(x => x.Resize(size, size));
+ // image.Mutate(x => x.DrawImage(backgroundImage, new Point(0, 0), 1));
+ // break;
+ // case BackgroundImageStyle.DataAreaOnly:
+ // var bgOffset = 4 - offset;
+ // backgroundImage = backgroundImage.Clone(x => x.Resize(size - (2 * bgOffset * pixelsPerModule), size - (2 * bgOffset * pixelsPerModule)));
+ // image.Mutate(x => x.DrawImage(backgroundImage, new Point(bgOffset * pixelsPerModule, bgOffset * pixelsPerModule), 1));
+ // break;
+ // }
+ //}
+
+ for (var x = 0; x < numModules; x += 1)
+ {
+ for (var y = 0; y < numModules; y += 1)
+ {
+ var rectangleF = SKRect.Create(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule); //creates a rectangle in positions x * pixelsPerModule, y * pixelsPerModule and with the width, height pixelsPerModule. Do not use the constructor since that uses the 4 point location.
+
+ var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x];
+ var solidBrush = pixelIsDark ? darkBrush : lightBrush;
+ //var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;
+
+ if (!IsPartOfFinderPattern(x, y, numModules, offset))
+ if (drawQuietZones && IsPartOfQuietZone(x, y, numModules))
+ canvas.DrawRect(rectangleF, solidBrush); // .Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ else
+ canvas.DrawRect(rectangleF, solidBrush); // .Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ else
+ canvas.DrawRect(rectangleF, solidBrush); // .Mutate(im => im.Fill(options, solidBrush, rectangleF));
+ }
+ }
+
+ if (logoImage != null)
+ {
+ var logoSize = (int)(size * 0.15);
+ var logoOffset = drawQuietZones ? 4 : 0;
+
+ var locationPadding = logoLocation switch
+ {
+ LogoLocation.Center => new SKPoint(size / 2, size / 2),
+ _ => new SKPoint(size - logoSize / 2 - logoOffset * pixelsPerModule, size - logoSize / 2 - logoOffset * pixelsPerModule),
+ };
+
+ var locationLogo = logoLocation switch
+ {
+ LogoLocation.Center => new SKPoint(size / 2 - logoSize / 2, size / 2 - logoSize / 2),
+ _ => new SKPoint(size - logoSize - logoOffset * pixelsPerModule, size - logoSize - logoOffset * pixelsPerModule),
+ };
+
+
+
+ switch (logoBackgroundShape)
+ {
+ case LogoBackgroundShape.Circle:
+ canvas.DrawOval(locationPadding, new SKSize(1.20f*logoSize / 2, 1.20f * logoSize/2), backgroundBrush);
+
+ break;
+ case LogoBackgroundShape.Rectangle:
+ var paddingRectangle = logoLocation switch
+ {
+ LogoLocation.Center => SKRect.Create(size / 2 - logoSize / 2, size / 2 - logoSize / 2, logoSize, logoSize),
+ _ => SKRect.Create(size - logoSize - logoOffset * pixelsPerModule, size - logoSize - logoOffset * pixelsPerModule, logoSize, logoSize)
+ };
+ paddingRectangle.Inflate(1.05f, 1.05f);
+ canvas.DrawRect(paddingRectangle, backgroundBrush);
+ break;
+ }
+
+
+ var destinationArea = logoLocation switch
+ {
+ LogoLocation.Center => SKRect.Create(size / 2 - logoSize / 2, size / 2 - logoSize / 2, logoSize, logoSize),
+ _ => SKRect.Create(size - logoSize - logoOffset * pixelsPerModule, size - logoSize - logoOffset * pixelsPerModule, logoSize, logoSize)
+ };
+ //var sourceArea = SKRect.Create(logoImage.Info.Width, logoImage.Info.Height);
+
+ canvas.DrawImage(logoImage, destinationArea);
+ }
+
+ return surface.Snapshot();
+ }
+
+ ///
+ /// Checks if a given module(-position) is part of the quietzone of a QR code
+ ///
+ /// X position
+ /// Y position
+ /// Total number of modules per row
+ /// true, if position is part of quiet zone
+ private bool IsPartOfQuietZone(int x, int y, int numModules)
+ {
+ return
+ x < 4 || //left
+ y < 4 || //top
+ x > numModules - 5 || //right
+ y > numModules - 5; //bottom
+ }
+
+
+ ///
+ /// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
+ ///
+ /// X position
+ /// Y position
+ /// Total number of modules per row
+ /// Offset in modules (usually depending on drawQuietZones parameter)
+ /// true, if position is part of any finder pattern
+ private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
+ {
+ var cornerSize = 11 - offset;
+ var outerLimitLow = (numModules - cornerSize - 1);
+ var outerLimitHigh = outerLimitLow + 8;
+ var invertedOffset = 4 - offset;
+ return
+ (x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
+ (x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
+ (x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
+ }
+ }
+}
diff --git a/QRCoder.SkiaSharpTests/Helpers/HelperFunctions.cs b/QRCoder.SkiaSharpTests/Helpers/HelperFunctions.cs
new file mode 100644
index 00000000..c0490310
--- /dev/null
+++ b/QRCoder.SkiaSharpTests/Helpers/HelperFunctions.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Text;
+using System.IO;
+using System.Security.Cryptography;
+
+#if !NETCOREAPP1_1
+using System.Drawing;
+#endif
+#if NETFRAMEWORK || NET5_0_WINDOWS
+using SW = System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+#endif
+
+
+namespace QRCoderTests.Helpers
+{
+ public static class HelperFunctions
+ {
+
+#if NETFRAMEWORK || NET5_0_WINDOWS
+ public static BitmapSource ToBitmapSource(DrawingImage source)
+ {
+ DrawingVisual drawingVisual = new DrawingVisual();
+ DrawingContext drawingContext = drawingVisual.RenderOpen();
+ drawingContext.DrawImage(source, new SW.Rect(new SW.Point(0, 0), new SW.Size(source.Width, source.Height)));
+ drawingContext.Close();
+
+ RenderTargetBitmap bmp = new RenderTargetBitmap((int)source.Width, (int)source.Height, 96, 96, PixelFormats.Pbgra32);
+ bmp.Render(drawingVisual);
+ return bmp;
+ }
+
+ public static Bitmap BitmapSourceToBitmap(DrawingImage xamlImg)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ PngBitmapEncoder encoder = new PngBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(ToBitmapSource(xamlImg)));
+ encoder.Save(ms);
+
+ using (Bitmap bmp = new Bitmap(ms))
+ {
+ return new Bitmap(bmp);
+ }
+ }
+ }
+#endif
+
+#if !NETCOREAPP1_1
+ public static string GetAssemblyPath()
+ {
+ return
+#if NET5_0 || NET6_0
+ AppDomain.CurrentDomain.BaseDirectory;
+#elif NET35 || NET452
+ Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Replace("file:\\", "");
+#else
+ Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location).Replace("file:\\", "");
+#endif
+ }
+#endif
+
+
+//#if NET6_0
+// public static string ImageToHash(SixLabors.ImageSharp.Image image)
+// {
+// using var ms = new MemoryStream();
+// image.Save(ms, new PngEncoder());
+// byte[] imgBytes = ms.ToArray();
+// return ByteArrayToHash(imgBytes);
+// }
+//#endif
+
+ public static string ByteArrayToHash(byte[] data)
+ {
+#if !NETCOREAPP1_1
+ var md5 = MD5.Create();
+ var hash = md5.ComputeHash(data);
+#else
+ var hash = new SshNet.Security.Cryptography.MD5().ComputeHash(data);
+#endif
+ return BitConverter.ToString(hash).Replace("-", "").ToLower();
+ }
+
+ public static string StringToHash(string data)
+ {
+ return ByteArrayToHash(Encoding.UTF8.GetBytes(data));
+ }
+ }
+}
diff --git a/QRCoder.SkiaSharpTests/QRCoder.SkiaSharpTests.csproj b/QRCoder.SkiaSharpTests/QRCoder.SkiaSharpTests.csproj
new file mode 100644
index 00000000..96d4b27f
--- /dev/null
+++ b/QRCoder.SkiaSharpTests/QRCoder.SkiaSharpTests.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/QRCoder.SkiaSharpTests/SkiaSharpTests.cs b/QRCoder.SkiaSharpTests/SkiaSharpTests.cs
new file mode 100644
index 00000000..22a6c7af
--- /dev/null
+++ b/QRCoder.SkiaSharpTests/SkiaSharpTests.cs
@@ -0,0 +1,43 @@
+using QRCoder.SkiaSharp;
+using QRCoderTests.Helpers;
+using SkiaSharp;
+
+namespace QRCoder.SkiaSharpTests
+{
+ [TestClass]
+ public class SkiaSharpTests
+ {
+ [TestMethod]
+ public void TestStandardQrCodeGeneration()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var qrcode = new SkiaSharpQRCode(data);
+ using var image = qrcode.GetGraphic();
+ using var imageData = image.Encode(SKEncodedImageFormat.Png, 80);
+ using var stream = File.OpenWrite("qrcode_skia.png");
+
+ imageData.SaveTo(stream);
+
+ }
+
+ [TestMethod]
+ public void TestLogoQrCodeGeneration()
+ {
+ var gen = new QRCodeGenerator();
+ var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
+ var qrcode = new SkiaSharpQRCode(data);
+
+ var logoImagePath = HelperFunctions.GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png";
+
+ using var logoStream = File.OpenRead(logoImagePath);
+ using var logoImage = SKImage.FromEncodedData(logoStream);
+
+ using var image = qrcode.GetGraphic(60, SKColors.Black,SKColors.White, SKColors.Red, logoImage, Models.LogoLocation.Center, Models.LogoBackgroundShape.Circle);
+ using var imageData = image.Encode(SKEncodedImageFormat.Png, 80);
+ using var stream = File.OpenWrite("qrcode_skia_logo.png");
+
+ imageData.SaveTo(stream);
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder.SkiaSharpTests/Usings.cs b/QRCoder.SkiaSharpTests/Usings.cs
new file mode 100644
index 00000000..ab67c7ea
--- /dev/null
+++ b/QRCoder.SkiaSharpTests/Usings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file
diff --git a/QRCoder.SkiaSharpTests/assets/noun_Scientist_2909361.svg b/QRCoder.SkiaSharpTests/assets/noun_Scientist_2909361.svg
new file mode 100644
index 00000000..869a0e73
--- /dev/null
+++ b/QRCoder.SkiaSharpTests/assets/noun_Scientist_2909361.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/QRCoder.SkiaSharpTests/assets/noun_software engineer_2909346.png b/QRCoder.SkiaSharpTests/assets/noun_software engineer_2909346.png
new file mode 100644
index 00000000..bc100986
Binary files /dev/null and b/QRCoder.SkiaSharpTests/assets/noun_software engineer_2909346.png differ
diff --git a/QRCoder.sln b/QRCoder.sln
index 5e8de931..2768e04a 100644
--- a/QRCoder.sln
+++ b/QRCoder.sln
@@ -15,6 +15,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoderTests", "QRCoderTest
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoder.Xaml", "QRCoder.Xaml\QRCoder.Xaml.csproj", "{A7A7E073-2504-4BA2-A63B-87AC34174789}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoder.ImageSharp", "QRCoder.ImageSharp\QRCoder.ImageSharp.csproj", "{229BC36C-54D6-43FC-9D82-EE0D6FD9883C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoder.ImageSharpTests", "QRCoder.ImageSharpTests\QRCoder.ImageSharpTests.csproj", "{F5596C43-DDB9-47BC-BCA0-2308A59F630B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3E51E726-E6AB-492B-A37D-DF1CE024D54F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRCoder.SkiaSharp", "QRCoder.SkiaSharp\QRCoder.SkiaSharp.csproj", "{D339E382-4B87-4E78-B5B9-381F88F14B4F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRCoder.SkiaSharpTests", "QRCoder.SkiaSharpTests\QRCoder.SkiaSharpTests.csproj", "{8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -119,10 +129,79 @@ Global
{A7A7E073-2504-4BA2-A63B-87AC34174789}.Release|x64.Build.0 = Release|Any CPU
{A7A7E073-2504-4BA2-A63B-87AC34174789}.Release|x86.ActiveCfg = Release|Any CPU
{A7A7E073-2504-4BA2-A63B-87AC34174789}.Release|x86.Build.0 = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|ARM.Build.0 = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|x64.Build.0 = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Debug|x86.Build.0 = Debug|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|ARM.ActiveCfg = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|ARM.Build.0 = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|x64.ActiveCfg = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|x64.Build.0 = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|x86.ActiveCfg = Release|Any CPU
+ {229BC36C-54D6-43FC-9D82-EE0D6FD9883C}.Release|x86.Build.0 = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|ARM.Build.0 = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|x64.Build.0 = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Debug|x86.Build.0 = Debug|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|ARM.Build.0 = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|x64.ActiveCfg = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|x64.Build.0 = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|x86.ActiveCfg = Release|Any CPU
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B}.Release|x86.Build.0 = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|ARM.Build.0 = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|x64.Build.0 = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Debug|x86.Build.0 = Debug|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|ARM.ActiveCfg = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|ARM.Build.0 = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|x64.ActiveCfg = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|x64.Build.0 = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|x86.ActiveCfg = Release|Any CPU
+ {D339E382-4B87-4E78-B5B9-381F88F14B4F}.Release|x86.Build.0 = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|ARM.Build.0 = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|x64.Build.0 = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Debug|x86.Build.0 = Debug|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|ARM.ActiveCfg = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|ARM.Build.0 = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|x64.ActiveCfg = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|x64.Build.0 = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|x86.ActiveCfg = Release|Any CPU
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {1B51624B-9915-4ED6-8FC1-1B7C472246E5} = {3E51E726-E6AB-492B-A37D-DF1CE024D54F}
+ {F5596C43-DDB9-47BC-BCA0-2308A59F630B} = {3E51E726-E6AB-492B-A37D-DF1CE024D54F}
+ {8F2B4B7D-ACEE-45CE-B691-CA0B06EDAB95} = {3E51E726-E6AB-492B-A37D-DF1CE024D54F}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F1845CDF-5EE5-456F-B6C8-717E4E2284F4}
EndGlobalSection
diff --git a/QRCoder/Models/LogoBackgroundShape.cs b/QRCoder/Models/LogoBackgroundShape.cs
new file mode 100644
index 00000000..f61ffc0e
--- /dev/null
+++ b/QRCoder/Models/LogoBackgroundShape.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QRCoder.Models
+{
+ public enum LogoBackgroundShape
+ {
+ Circle,
+ Rectangle
+ }
+}
diff --git a/QRCoder/Models/LogoLocation.cs b/QRCoder/Models/LogoLocation.cs
new file mode 100644
index 00000000..4874125e
--- /dev/null
+++ b/QRCoder/Models/LogoLocation.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QRCoder.Models
+{
+ public enum LogoLocation
+ {
+ Center,
+ BottomRight
+ }
+}
diff --git a/QRCoderTests/ArtQRCodeRendererTests.cs b/QRCoderTests/ArtQRCodeRendererTests.cs
index bcd32c9f..fd31535e 100644
--- a/QRCoderTests/ArtQRCodeRendererTests.cs
+++ b/QRCoderTests/ArtQRCodeRendererTests.cs
@@ -21,7 +21,7 @@ public void can_create_standard_qrcode_graphic()
var gen = new QRCodeGenerator();
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
var bmp = new ArtQRCode(data).GetGraphic(10);
-
+ bmp.Save("original.png");
var result = HelperFunctions.BitmapToHash(bmp);
#if NET35_OR_GREATER || NET40_OR_GREATER
result.ShouldBe("11ebdda91b9632d016798cb6de2f5339");
@@ -38,7 +38,7 @@ public void can_create_standard_qrcode_graphic_with_custom_finder()
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
var finder = new Bitmap(15, 15);
var bmp = new ArtQRCode(data).GetGraphic(10, Color.Black, Color.White, Color.Transparent, finderPatternImage: finder);
-
+ bmp.Save("finder.png");
var result = HelperFunctions.BitmapToHash(bmp);
#if NET35_OR_GREATER || NET40_OR_GREATER
result.ShouldBe("c54a7389ae995abc838f0d228acc3bad");
@@ -71,7 +71,7 @@ public void can_create_standard_qrcode_graphic_with_background()
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
var bmp = new ArtQRCode(data).GetGraphic((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
-
+ bmp.Save("custom_background.png");
var result = HelperFunctions.BitmapToHash(bmp);
#if NET35_OR_GREATER || NET40_OR_GREATER
diff --git a/QRCoderTests/Helpers/HelperFunctions.cs b/QRCoderTests/Helpers/HelperFunctions.cs
index 5fd9aa13..484aa854 100644
--- a/QRCoderTests/Helpers/HelperFunctions.cs
+++ b/QRCoderTests/Helpers/HelperFunctions.cs
@@ -2,6 +2,7 @@
using System.Text;
using System.IO;
using System.Security.Cryptography;
+
#if !NETCOREAPP1_1
using System.Drawing;
#endif
diff --git a/QRCoderTests/QRCoderTests.csproj b/QRCoderTests/QRCoderTests.csproj
index 3f1aa274..751a5775 100644
--- a/QRCoderTests/QRCoderTests.csproj
+++ b/QRCoderTests/QRCoderTests.csproj
@@ -58,6 +58,9 @@
Always
+
+
+
$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client
false
diff --git a/QRCoderTests/SvgQRCodeRendererTests.cs b/QRCoderTests/SvgQRCodeRendererTests.cs
index b0620594..709cd618 100644
--- a/QRCoderTests/SvgQRCodeRendererTests.cs
+++ b/QRCoderTests/SvgQRCodeRendererTests.cs
@@ -117,7 +117,7 @@ public void can_render_svg_qrcode_with_png_logo()
logoObj.GetMediaType().ShouldBe(SvgQRCode.SvgLogo.MediaType.PNG);
var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj);
-
+
var result = HelperFunctions.StringToHash(svg);
result.ShouldBe("78e02e8ba415f15817d5ed88c4afca31");
}