diff --git a/QRCoder/QRCoder.csproj b/QRCoder/QRCoder.csproj index d67a7989..445e4aa8 100644 --- a/QRCoder/QRCoder.csproj +++ b/QRCoder/QRCoder.csproj @@ -1,63 +1,74 @@  - - net35;net40;netstandard1.3;netstandard2.0;net5.0;net5.0-windows;net6.0;net6.0-windows - false - $(DefineConstants);NET5_0_WINDOWS - $(DefineConstants);NET6_0_WINDOWS - false - true - - - - - - - - false - QRCoder - 1.4.3 - Raffael Herrmann - Raffael Herrmann - QRCoder - MIT - https://github.com/codebude/QRCoder/ - nuget-icon.png - nuget-readme.md - c# csharp qr qrcoder qrcode qr-generator qr-code-generator - https://github.com/codebude/QRCoder.git - git - QRCoder is a simple library, written in C#.NET, which enables you to create QR codes. - - - - - - - - - - - - - - - - - - - - + + net35;net40;netstandard1.3;netstandard2.0;net5.0;net5.0-windows;net6.0;net6.0-windows + false + $(DefineConstants);NET5_0_WINDOWS + $(DefineConstants);NET6_0_WINDOWS + false + true + + + + + + + + false + QRCoder + 1.4.3 + Raffael Herrmann + Raffael Herrmann + QRCoder + MIT + https://github.com/codebude/QRCoder/ + nuget-icon.png + nuget-readme.md + c# csharp qr qrcoder qrcode qr-generator qr-code-generator + https://github.com/codebude/QRCoder.git + git + QRCoder is a simple library, written in C#.NET, which enables you to create QR codes. + + + + + + + + + + + + + + + + + + + + + + 2.88.3 + + + + + 2.88.3 + - - $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client - false - true - QRCoderStrongName.snk - false - + + + + + $(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client + false + true + QRCoderStrongName.snk + false + diff --git a/QRCoder/SvgQRCode_NET6.cs b/QRCoder/SvgQRCode_NET6.cs new file mode 100644 index 00000000..a8a55325 --- /dev/null +++ b/QRCoder/SvgQRCode_NET6.cs @@ -0,0 +1,384 @@ +#if NET6_0 +using QRCoder.Extensions; +using SkiaSharp; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static QRCoder.QRCodeGenerator; +using static QRCoder.SvgQRCode_NET6; + +namespace QRCoder +{ + /// + /// There class is basically the same as SvgQrCode but removed all System.Drawing + /// + public class SvgQRCode_NET6 : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public SvgQRCode_NET6() { } + public SvgQRCode_NET6(QRCodeData data) : base(data) { } + public string GetGraphic(int pixelsPerModule) + { + var viewBox = new SKSize(pixelsPerModule * this.QrCodeData.ModuleMatrix.Count, pixelsPerModule * this.QrCodeData.ModuleMatrix.Count); + return this.GetGraphic(viewBox, SKColors.Black, SKColors.White); + } + + /// + /// Returns a QR code as SVG string with custom colors, optional quietzone and logo + /// + /// The pixel size each b/w module is drawn + /// Color of the dark modules + /// Color of the light modules + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(int pixelsPerModule, SKColor darkColor, SKColor lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null) + { + var offset = drawQuietZones ? 0 : 4; + var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule); + var viewBox = new SKSize(edgeSize, edgeSize); + return this.GetGraphic(viewBox, darkColor.ToString(), lightColor.ToString(), drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo + /// + /// The pixel size each b/w module is drawn + /// The color of the dark/black modules in hex (e.g. #000000) representation + /// The color of the light/white modules in hex (e.g. #ffffff) representation + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null) + { + var offset = drawQuietZones ? 0 : 4; + var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule); + var viewBox = new SKSize(edgeSize, edgeSize); + return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// The color of the dark/black modules in hex (e.g. #000000) representation + /// The color of the light/white modules in hex (e.g. #ffffff) representation + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(SKSize viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null) + { + int offset = drawQuietZones ? 0 : 4; + int drawableModulesCount = this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : offset * 2); + double pixelsPerModule = Math.Min(viewBox.Width, viewBox.Height) / (double)drawableModulesCount; + double qrSize = drawableModulesCount * pixelsPerModule; + string svgSizeAttributes = (sizingMode == SizingMode.WidthHeightAttribute) ? $@"width=""{viewBox.Width}"" height=""{viewBox.Height}""" : $@"viewBox=""0 0 {viewBox.Width} {viewBox.Height}"""; + ImageAttributes? logoAttr = null; + if (logo != null) + logoAttr = GetLogoAttributes(logo, viewBox); + + // Merge horizontal rectangles + int[,] matrix = new int[drawableModulesCount, drawableModulesCount]; + for (int yi = 0; yi < drawableModulesCount; yi += 1) + { + BitArray bitArray = this.QrCodeData.ModuleMatrix[yi + offset]; + + int x0 = -1; + int xL = 0; + for (int xi = 0; xi < drawableModulesCount; xi += 1) + { + matrix[yi, xi] = 0; + if (bitArray[xi + offset] && (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo((xi + offset) * pixelsPerModule, (yi + offset) * pixelsPerModule, logoAttr, pixelsPerModule))) + { + if (x0 == -1) + { + x0 = xi; + } + xL += 1; + } + else + { + if (xL > 0) + { + matrix[yi, x0] = xL; + x0 = -1; + xL = 0; + } + } + } + + if (xL > 0) + { + matrix[yi, x0] = xL; + } + } + + StringBuilder svgFile = new StringBuilder($@""); + svgFile.AppendLine($@""); + for (int yi = 0; yi < drawableModulesCount; yi += 1) + { + double y = yi * pixelsPerModule; + for (int xi = 0; xi < drawableModulesCount; xi += 1) + { + int xL = matrix[yi, xi]; + if (xL > 0) + { + // Merge vertical rectangles + int yL = 1; + for (int y2 = yi + 1; y2 < drawableModulesCount; y2 += 1) + { + if (matrix[y2, xi] == xL) + { + matrix[y2, xi] = 0; + yL += 1; + } + else + { + break; + } + } + + // Output SVG rectangles + double x = xi * pixelsPerModule; + if (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo(x, y, logoAttr, pixelsPerModule)) + svgFile.AppendLine($@""); + } + } + } + + //Render logo, if set + if (logo != null) + { + if (!logo.IsEmbedded()) + { + svgFile.AppendLine($@""); + svgFile.AppendLine($@""); + svgFile.AppendLine(@""); + } + else + { + var rawLogo = (string)logo.GetRawLogo(); + var svg = System.Xml.Linq.XDocument.Parse(rawLogo); + svg.Root.SetAttributeValue("x", CleanSvgVal(logoAttr.Value.X)); + svg.Root.SetAttributeValue("y", CleanSvgVal(logoAttr.Value.Y)); + svg.Root.SetAttributeValue("width", CleanSvgVal(logoAttr.Value.Width)); + svg.Root.SetAttributeValue("height", CleanSvgVal(logoAttr.Value.Height)); + svg.Root.SetAttributeValue("shape-rendering", "geometricPrecision"); + svgFile.AppendLine(svg.ToString(System.Xml.Linq.SaveOptions.DisableFormatting).Replace("svg:", "")); + } + } + + svgFile.Append(@""); + return svgFile.ToString(); + } + + /// + /// Returns a QR code as SVG string with optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(SKSize viewBox, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null) + { + return this.GetGraphic(viewBox, SKColors.Black, SKColors.White, drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with custom colors and optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// Color of the dark modules + /// Color of the light modules + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(SKSize viewBox, SKColor darkColor, SKColor lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null) + { + return this.GetGraphic(viewBox, darkColor.ToString(), lightColor.ToString(), drawQuietZones, sizingMode, logo); + } + + private bool IsBlockedByLogo(double x, double y, ImageAttributes? attr, double pixelPerModule) + { + return x + pixelPerModule >= attr.Value.X && x <= attr.Value.X + attr.Value.Width && y + pixelPerModule >= attr.Value.Y && y <= attr.Value.Y + attr.Value.Height; + } + + private ImageAttributes GetLogoAttributes(SvgLogo_NET6 logo, SKSize viewBox) + { + var imgWidth = logo.GetIconSizePercent() / 100d * viewBox.Width; + var imgHeight = logo.GetIconSizePercent() / 100d * viewBox.Height; + var imgPosX = viewBox.Width / 2d - imgWidth / 2d; + var imgPosY = viewBox.Height / 2d - imgHeight / 2d; + return new ImageAttributes() + { + Width = imgWidth, + Height = imgHeight, + X = imgPosX, + Y = imgPosY + }; + } + + private struct ImageAttributes + { + public double Width; + public double Height; + public double X; + public double Y; + } + + private string CleanSvgVal(double input) + { + //Clean double values for international use/formats + //We use explicitly "G15" to avoid differences between .NET full and Core platforms + //https://stackoverflow.com/questions/64898117/tostring-has-a-different-behavior-between-net-462-and-net-core-3-1 + return input.ToString("G15", System.Globalization.CultureInfo.InvariantCulture); + } + + /// + /// Mode of sizing attribution on svg root node + /// + public enum SizingMode + { + WidthHeightAttribute, + ViewBoxAttribute + } + + /// + /// Represents a logo graphic that can be rendered on a SvgQRCode_NET6 + /// + public class SvgLogo_NET6 + { + private string _logoData; + private MediaType _mediaType; + private int _iconSizePercent; + private bool _fillLogoBackground; + private object _logoRaw; + private bool _isEmbedded; + + + /// + /// Create a logo object to be used in SvgQRCode_NET6 renderer + /// + /// Logo to be rendered as Bitmap/rasterized graphic + /// Degree of percentage coverage of the QR code by the logo + /// If true, the background behind the logo will be cleaned + public SvgLogo_NET6(SKImage iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true) + { + _iconSizePercent = iconSizePercent; + using (var ms = new System.IO.MemoryStream()) + { + iconRasterized.Encode(SKEncodedImageFormat.Png, 100).SaveTo(ms); + _logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None); + } + + _mediaType = MediaType.PNG; + _fillLogoBackground = fillLogoBackground; + _logoRaw = iconRasterized; + _isEmbedded = false; + } + + /// + /// Create a logo object to be used in SvgQRCode_NET6 renderer + /// + /// Logo to be rendered as SVG/vectorized graphic/string + /// Degree of percentage coverage of the QR code by the logo + /// If true, the background behind the logo will be cleaned + /// If true, the logo will embedded as native svg instead of embedding it as image-tag + public SvgLogo_NET6(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true, bool iconEmbedded = true) + { + _iconSizePercent = iconSizePercent; + _logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None); + _mediaType = MediaType.SVG; + _fillLogoBackground = fillLogoBackground; + _logoRaw = iconVectorized; + _isEmbedded = iconEmbedded; + } + + /// + /// Returns the raw logo's data + /// + /// + public object GetRawLogo() + { + return _logoRaw; + } + + /// + /// Defines, if the logo shall be natively embedded. + /// true=native svg embedding, false=embedding via image-tag + /// + /// + public bool IsEmbedded() + { + return _isEmbedded; + } + + /// + /// Returns the media type of the logo + /// + /// + public MediaType GetMediaType() + { + return _mediaType; + } + + /// + /// Returns the logo as data-uri + /// + /// + public string GetDataUri() + { + return $"data:{_mediaType.GetStringValue()};base64,{_logoData}"; + } + + /// + /// Returns how much of the QR code should be covered by the logo (in percent) + /// + /// + public int GetIconSizePercent() + { + return _iconSizePercent; + } + + /// + /// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo) + /// + /// + public bool FillLogoBackground() + { + return _fillLogoBackground; + } + + /// + /// Media types for SvgLogo_NET6s + /// + public enum MediaType : int + { + [StringValue("image/png")] + PNG = 0, + [StringValue("image/svg+xml")] + SVG = 1 + } + } + } + public static class SvgQRCode_NET6Helper + { + public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorHex, string lightColorHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new SvgQRCode_NET6(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); + } + } +} +#endif \ No newline at end of file