├── QRCoder ├── Assets │ ├── nuget-icon.png │ └── nuget-readme.md ├── Exceptions │ └── DataTooLongException.cs ├── AbstractQRCode.cs ├── Extensions │ └── StringValueAttribute.cs ├── QRCoder.csproj ├── Base64QRCode.cs ├── ASCIIQRCode.cs ├── BitmapByteQRCode.cs ├── PostscriptQRCode.cs ├── QRCodeData.cs ├── ArtQRCode.cs ├── PdfByteQRCode.cs ├── QRCode.cs ├── PngByteQRCode.cs └── SvgQRCode.cs ├── QRCoderTests ├── assets │ ├── noun_software engineer_2909346.png │ └── noun_Scientist_2909361.svg ├── Helpers │ ├── CategoryDiscoverer.cs │ └── HelperFunctions.cs ├── QRCoderTests.csproj ├── XamlQRCodeRendererTests.cs ├── PngByteQRCodeRendererTests.cs ├── QRCodeRendererTests.cs ├── SvgQRCodeRendererTests.cs ├── QRGeneratorTests.cs └── AsciiQRCodeRendererTests.cs ├── .github └── workflows │ └── wf-test.yml ├── LICENSE.txt ├── readme.md ├── QRCoder-ImageSharp.sln └── .gitignore /QRCoder/Assets/nuget-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JPlenert/QRCoder-ImageSharp/HEAD/QRCoder/Assets/nuget-icon.png -------------------------------------------------------------------------------- /QRCoderTests/assets/noun_software engineer_2909346.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JPlenert/QRCoder-ImageSharp/HEAD/QRCoderTests/assets/noun_software engineer_2909346.png -------------------------------------------------------------------------------- /.github/workflows/wf-test.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup dotnet 13 | uses: actions/setup-dotnet@v1 14 | with: 15 | dotnet-version: | 16 | 6.0.x 17 | - name: Setup dotnet 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: | 21 | 8.0.x 22 | - run: dotnet build 23 | - run: dotnet test -f net6.0 24 | - run: dotnet test -f net8.0 25 | -------------------------------------------------------------------------------- /QRCoder/Exceptions/DataTooLongException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QRCoder.Exceptions 4 | { 5 | public class DataTooLongException : Exception 6 | { 7 | public DataTooLongException(string eccLevel, string encodingMode, int maxSizeByte) : base( 8 | $"The given payload exceeds the maximum size of the QR code standard. The maximum size allowed for the choosen paramters (ECC level={eccLevel}, EncodingMode={encodingMode}) is {maxSizeByte} byte." 9 | ){} 10 | 11 | public DataTooLongException(string eccLevel, string encodingMode, int version, int maxSizeByte) : base( 12 | $"The given payload exceeds the maximum size of the QR code standard. The maximum size allowed for the choosen paramters (ECC level={eccLevel}, EncodingMode={encodingMode}, FixedVersion={version}) is {maxSizeByte} byte." 13 | ) 14 | { } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /QRCoder/AbstractQRCode.cs: -------------------------------------------------------------------------------- 1 | namespace QRCoder 2 | { 3 | public abstract class AbstractQRCode 4 | { 5 | protected QRCodeData QrCodeData { get; set; } 6 | 7 | protected AbstractQRCode() { 8 | } 9 | 10 | protected AbstractQRCode(QRCodeData data) { 11 | this.QrCodeData = data; 12 | } 13 | 14 | /// 15 | /// Set a QRCodeData object that will be used to generate QR code. Used in COM Objects connections 16 | /// 17 | /// Need a QRCodeData object generated by QRCodeGenerator.CreateQrCode() 18 | virtual public void SetQRCodeData(QRCodeData data) { 19 | this.QrCodeData = data; 20 | } 21 | 22 | public void Dispose() 23 | { 24 | this.QrCodeData?.Dispose(); 25 | this.QrCodeData = null; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright for portions of the QRCoder are held by (c) Raffael Herrmann, 2013-2018. 4 | All other copyright for enhancements and migration to ImageSharp are held by Joerg Plenert, 2022. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # QRCoder-ImageSharp 2 | 3 | ## Info 4 | 5 | QRCoder-ImageSharp is a simple library, written in C#.NET, which enables you to create QR codes. 6 | It was forked from the [QRCoder](https://github.com/codebude/QRCoder) project that is using the System.Drawing assembly. 7 | 8 | Because of the System.Drawing assembly does not suport [non-Windows](https://docs.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only) plattforms, QRCoder-ImageSharp is using the [ImageSharp](https://github.com/SixLabors/ImageSharp) assembly to support more plattforms. 9 | 10 | ## Differences between QRCoder and QRCoder-ImageSharp 11 | 12 | - QRCoder-ImageSharp is using ImageSharp instead of System.Drawing 13 | - QRCoder-ImageSharp is not supporting ArtQRCode 14 | - QRCoder-ImageSharp does not make rounded ractangles when including logos into QRCode 15 | - QRCoder-ImageSharp does not support russian QRCode payloads (we stand with ukraine!) 16 | 17 | ## Legal information and credits 18 | 19 | QRCoder is a project by [Raffael Herrmann](https://raffaelherrmann.de) and was first released in 10/2013. 20 | QRCoder-ImageSharp is a project by [Joerg Plenert](https://plenert.net). It's licensed under the [MIT license](https://github.com/JPlenert/QRCoder.ImageSharp/blob/master/license.txt). -------------------------------------------------------------------------------- /QRCoder/Assets/nuget-readme.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | QRCoder-ImageSharp is a simple library, written in C#.NET, which enables you to create QR codes. 4 | It was forked from the [QRCoder](https://github.com/codebude/QRCoder) project that is using the System.Drawing assembly. 5 | 6 | Because of the System.Drawing assembly does not suport [non-Windows](https://docs.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only) plattforms, QRCoder-ImageSharp is using the [ImageSharp](https://github.com/SixLabors/ImageSharp) assembly to support more plattforms. 7 | 8 | *** 9 | 10 | ## Differences between QRCoder and QRCoder-ImageSharp 11 | 12 | - QRCoder-ImageSharp is using ImageSharp instead of System.Drawing 13 | - QRCoder-ImageSharp is not supporting ArtQRCode 14 | - QRCoder-ImageSharp does not make rounded ractangles when including logos into QRCode 15 | - QRCoder-ImageSharp does not support russian QRCode payloads (we stand with ukraine!) 16 | 17 | ## Legal information and credits 18 | 19 | QRCoder is a project by [Raffael Herrmann](https://raffaelherrmann.de) and was first released in 10/2013. 20 | QRCoder-ImageSharp is a project by [Joerg Plenert](https://plenert.net). It's licensed under the [MIT license](https://github.com/JPlenert/QRCoder.ImageSharp/blob/master/license.txt). -------------------------------------------------------------------------------- /QRCoderTests/Helpers/CategoryDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | #if !NET35 && !NET452 5 | using Xunit.Abstractions; 6 | #endif 7 | using Xunit.Sdk; 8 | 9 | namespace QRCoderTests.Helpers.XUnitExtenstions 10 | { 11 | #if NET35 || NET452 12 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 13 | public class CategoryAttribute : Attribute 14 | { 15 | public CategoryAttribute(string category) { } 16 | } 17 | #else 18 | public class CategoryDiscoverer : ITraitDiscoverer 19 | { 20 | public const string KEY = "Category"; 21 | 22 | public IEnumerable> GetTraits(IAttributeInfo traitAttribute) 23 | { 24 | var ctorArgs = traitAttribute.GetConstructorArguments().ToList(); 25 | yield return new KeyValuePair(KEY, ctorArgs[0].ToString()); 26 | } 27 | } 28 | 29 | //NOTICE: Take a note that you must provide appropriate namespace here 30 | [TraitDiscoverer("QRCoderTests.XUnitExtenstions.CategoryDiscoverer", "QRCoderTests")] 31 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 32 | public class CategoryAttribute : Attribute, ITraitAttribute 33 | { 34 | public CategoryAttribute(string category) { } 35 | } 36 | #endif 37 | } 38 | -------------------------------------------------------------------------------- /QRCoder/Extensions/StringValueAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace QRCoder.Extensions 8 | { 9 | /// 10 | /// Used to represent a string value for a value in an enum 11 | /// 12 | public class StringValueAttribute : Attribute 13 | { 14 | 15 | #region Properties 16 | 17 | /// 18 | /// Holds the alue in an enum 19 | /// 20 | public string StringValue { get; protected set; } 21 | 22 | #endregion 23 | 24 | /// 25 | /// Init a StringValue Attribute 26 | /// 27 | /// 28 | public StringValueAttribute(string value) 29 | { 30 | this.StringValue = value; 31 | } 32 | } 33 | 34 | public static class CustomExtensions 35 | { 36 | /// 37 | /// Will get the string value for a given enum's value 38 | /// 39 | /// 40 | /// 41 | public static string GetStringValue(this Enum value) 42 | { 43 | var fieldInfo = value.GetType().GetField(value.ToString()); 44 | var attr = fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[]; 45 | return attr.Length > 0 ? attr[0].StringValue : null; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /QRCoderTests/QRCoderTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;net6.0 4 | false 5 | true 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Never 15 | 16 | 17 | Never 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /QRCoderTests/XamlQRCodeRendererTests.cs: -------------------------------------------------------------------------------- 1 | #if NETFRAMEWORK || NET5_0_WINDOWS || NET6_0_WINDOWS 2 | using Xunit; 3 | using QRCoder; 4 | using QRCoder.Xaml; 5 | using Shouldly; 6 | using QRCoderTests.Helpers.XUnitExtenstions; 7 | using QRCoderTests.Helpers; 8 | 9 | namespace QRCoderTests 10 | { 11 | 12 | public class XamlQRCodeRendererTests 13 | { 14 | 15 | [Fact] 16 | [Category("QRRenderer/XamlQRCode")] 17 | public void can_create_xaml_qrcode_standard_graphic() 18 | { 19 | var gen = new QRCodeGenerator(); 20 | var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); 21 | var xCode = new XamlQRCode(data).GetGraphic(10); 22 | 23 | var bmp = HelperFunctions.BitmapSourceToBitmap(xCode); 24 | var result = HelperFunctions.BitmapToHash(bmp); 25 | result.ShouldBe("e8c61b8f0455924fe08ba68686d0d296"); 26 | } 27 | 28 | 29 | [Fact] 30 | [Category("QRRenderer/XamlQRCode")] 31 | public void can_instantate_qrcode_parameterless() 32 | { 33 | var svgCode = new XamlQRCode(); 34 | svgCode.ShouldNotBeNull(); 35 | svgCode.ShouldBeOfType(); 36 | } 37 | 38 | /* 39 | [Fact] 40 | [Category("QRRenderer/XamlQRCode")] 41 | public void can_render_qrcode_from_helper() 42 | { 43 | //Create QR code 44 | var bmp = QRCodeHelper.GetQRCode("This is a quick test! 123#?", 10, Color.Black, Color.White, QRCodeGenerator.ECCLevel.H); 45 | 46 | var result = HelperFunctions.BitmapToHash(bmp); 47 | result.ShouldBe("e8c61b8f0455924fe08ba68686d0d296"); 48 | } 49 | */ 50 | } 51 | } 52 | #endif -------------------------------------------------------------------------------- /QRCoder/QRCoder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;netstandard2.1;net8.0 5 | false 6 | true 7 | 8 | 9 | 10 | QRCoder-ImageSharp 11 | 0.10.0 12 | Raffael Herrmann, Joerg Plenert 13 | Joerg Plenert 14 | QRCoder 15 | MIT 16 | https://github.com/JPlenert/QRCoder-ImageSharp 17 | nuget-icon.png 18 | nuget-readme.md 19 | c# csharp qr qrcoder qrcode qr-generator qr-code-generator 20 | https://github.com/JPlenert/QRCoder-ImageSharp.git 21 | git 22 | QRCoder is a simple library, written in C#.NET, which enables you to create QR codes. Based on ImageSharp to provide plattform independency. 23 | true 24 | Raffael Herrmann, Joerg Plenert 25 | Based on version 1.4.3 of QRCoder 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /QRCoder-ImageSharp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoder", "QRCoder\QRCoder.csproj", "{AA6BE23A-7813-4D2A-835E-B673631AE9F1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoderTests", "QRCoderTests\QRCoderTests.csproj", "{1B51624B-9915-4ED6-8FC1-1B7C472246E5}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|ARM = Debug|ARM 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|ARM = Release|ARM 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|ARM.ActiveCfg = Debug|Any CPU 25 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|ARM.Build.0 = Debug|Any CPU 26 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|x64.Build.0 = Debug|Any CPU 28 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Debug|x86.Build.0 = Debug|Any CPU 30 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|ARM.ActiveCfg = Release|Any CPU 33 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|ARM.Build.0 = Release|Any CPU 34 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|x64.ActiveCfg = Release|Any CPU 35 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|x64.Build.0 = Release|Any CPU 36 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|x86.ActiveCfg = Release|Any CPU 37 | {AA6BE23A-7813-4D2A-835E-B673631AE9F1}.Release|x86.Build.0 = Release|Any CPU 38 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|ARM.ActiveCfg = Debug|Any CPU 41 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|ARM.Build.0 = Debug|Any CPU 42 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|x64.ActiveCfg = Debug|Any CPU 43 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|x64.Build.0 = Debug|Any CPU 44 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|x86.ActiveCfg = Debug|Any CPU 45 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Debug|x86.Build.0 = Debug|Any CPU 46 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|ARM.ActiveCfg = Release|Any CPU 49 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|ARM.Build.0 = Release|Any CPU 50 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x64.ActiveCfg = Release|Any CPU 51 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x64.Build.0 = Release|Any CPU 52 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x86.ActiveCfg = Release|Any CPU 53 | {1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x86.Build.0 = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {F1845CDF-5EE5-456F-B6C8-717E4E2284F4} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | .vs/ 44 | 45 | # Build results 46 | 47 | [Dd]ebug/ 48 | [Rr]elease/ 49 | x64/ 50 | build/ 51 | [Bb]in/ 52 | [Oo]bj/ 53 | 54 | # MSTest test Results 55 | [Tt]est[Rr]esult*/ 56 | [Bb]uild[Ll]og.* 57 | 58 | *_i.c 59 | *_p.c 60 | *.ilk 61 | *.meta 62 | *.obj 63 | *.pch 64 | *.pdb 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.log 80 | *.scc 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | *.ncrunch* 110 | .*crunch*.local.xml 111 | 112 | # Installshield output folder 113 | [Ee]xpress/ 114 | 115 | # DocProject is a documentation generator add-in 116 | DocProject/buildhelp/ 117 | DocProject/Help/*.HxT 118 | DocProject/Help/*.HxC 119 | DocProject/Help/*.hhc 120 | DocProject/Help/*.hhk 121 | DocProject/Help/*.hhp 122 | DocProject/Help/Html2 123 | DocProject/Help/html 124 | 125 | # Click-Once directory 126 | publish/ 127 | 128 | # Publish Web Output 129 | *.Publish.xml 130 | *.pubxml 131 | 132 | # NuGet Packages Directory 133 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 134 | packages/ 135 | 136 | # Windows Azure Build Output 137 | csx 138 | *.build.csdef 139 | 140 | # Windows Store app package directory 141 | AppPackages/ 142 | 143 | # Others 144 | sql/ 145 | *.Cache 146 | ClientBin/ 147 | [Ss]tyle[Cc]op.* 148 | ~$* 149 | *~ 150 | *.dbmdl 151 | *.[Pp]ublish.xml 152 | *.pfx 153 | *.publishsettings 154 | 155 | # RIA/Silverlight projects 156 | Generated_Code/ 157 | 158 | # Backup & report files from converting an old project file to a newer 159 | # Visual Studio version. Backup files are not needed, because we have git ;-) 160 | _UpgradeReport_Files/ 161 | Backup*/ 162 | UpgradeLog*.XML 163 | UpgradeLog*.htm 164 | 165 | # SQL Server files 166 | App_Data/*.mdf 167 | App_Data/*.ldf 168 | 169 | ############# 170 | ## Windows detritus 171 | ############# 172 | 173 | # Windows image file caches 174 | Thumbs.db 175 | ehthumbs.db 176 | 177 | # Folder config file 178 | Desktop.ini 179 | 180 | # Recycle Bin used on file shares 181 | $RECYCLE.BIN/ 182 | 183 | # Mac crap 184 | .DS_Store 185 | 186 | 187 | ############# 188 | ## Python 189 | ############# 190 | 191 | *.py[co] 192 | 193 | # Packages 194 | *.egg 195 | *.egg-info 196 | dist/ 197 | build/ 198 | eggs/ 199 | parts/ 200 | var/ 201 | sdist/ 202 | develop-eggs/ 203 | .installed.cfg 204 | 205 | # Installer logs 206 | pip-log.txt 207 | 208 | # Unit test / coverage reports 209 | .coverage 210 | .tox 211 | 212 | #Translations 213 | *.mo 214 | 215 | #Mr Developer 216 | .mr.developer.cfg 217 | 218 | # Xamarin 219 | *.userprefs 220 | 221 | QRCoder/PortabilityAnalysis.html 222 | -------------------------------------------------------------------------------- /QRCoder/Base64QRCode.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using SixLabors.ImageSharp.Formats; 3 | using System; 4 | using System.IO; 5 | using static QRCoder.Base64QRCode; 6 | using static QRCoder.QRCodeGenerator; 7 | 8 | namespace QRCoder 9 | { 10 | public class Base64QRCode : AbstractQRCode, IDisposable 11 | { 12 | private QRCode qr; 13 | 14 | /// 15 | /// Constructor without params to be used in COM Objects connections 16 | /// 17 | public Base64QRCode() { 18 | qr = new QRCode(); 19 | } 20 | 21 | public Base64QRCode(QRCodeData data) : base(data) { 22 | qr = new QRCode(data); 23 | } 24 | 25 | public override void SetQRCodeData(QRCodeData data) { 26 | this.qr.SetQRCodeData(data); 27 | } 28 | 29 | public string GetGraphic(int pixelsPerModule) 30 | { 31 | return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, true); 32 | } 33 | 34 | 35 | public string GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, bool drawQuietZones = true, ImageType imgType = ImageType.Png) 36 | { 37 | return this.GetGraphic(pixelsPerModule, Color.Parse(darkColorHtmlHex), Color.Parse(lightColorHtmlHex), drawQuietZones, imgType); 38 | } 39 | 40 | public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, ImageType imgType = ImageType.Png) 41 | { 42 | var base64 = string.Empty; 43 | using (Image img = qr.GetGraphic(pixelsPerModule, darkColor, lightColor, drawQuietZones)) 44 | { 45 | base64 = BitmapToBase64(img, imgType); 46 | } 47 | return base64; 48 | } 49 | 50 | public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Image icon, int iconSizePercent = 15, int iconBorderWidth = 6, bool drawQuietZones = true, ImageType imgType = ImageType.Png) 51 | { 52 | var base64 = string.Empty; 53 | using (Image bmp = qr.GetGraphic(pixelsPerModule, darkColor, lightColor, icon, iconSizePercent, iconBorderWidth, drawQuietZones)) 54 | { 55 | base64 = BitmapToBase64(bmp, imgType); 56 | } 57 | return base64; 58 | } 59 | 60 | 61 | private string BitmapToBase64(Image img, ImageType imgType) 62 | { 63 | var base64 = string.Empty; 64 | IImageEncoder iFormat; 65 | switch (imgType) { 66 | default: 67 | case ImageType.Png: 68 | iFormat = new SixLabors.ImageSharp.Formats.Png.PngEncoder(); 69 | break; 70 | case ImageType.Jpeg: 71 | iFormat = new SixLabors.ImageSharp.Formats.Jpeg.JpegEncoder(); 72 | break; 73 | case ImageType.Gif: 74 | iFormat = new SixLabors.ImageSharp.Formats.Gif.GifEncoder(); 75 | break; 76 | } 77 | using (MemoryStream memoryStream = new MemoryStream()) 78 | { 79 | img.Save(memoryStream, iFormat); 80 | base64 = Convert.ToBase64String(memoryStream.ToArray(), Base64FormattingOptions.None); 81 | } 82 | return base64; 83 | } 84 | 85 | public enum ImageType 86 | { 87 | Gif, 88 | Jpeg, 89 | Png 90 | } 91 | 92 | } 93 | 94 | public static class Base64QRCodeHelper 95 | { 96 | public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, ImageType imgType = ImageType.Png) 97 | { 98 | using (var qrGenerator = new QRCodeGenerator()) 99 | using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) 100 | using (var qrCode = new Base64QRCode(qrCodeData)) 101 | return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex, drawQuietZones, imgType); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /QRCoder/ASCIIQRCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using static QRCoder.QRCodeGenerator; 5 | 6 | namespace QRCoder 7 | { 8 | public class AsciiQRCode : AbstractQRCode, IDisposable 9 | { 10 | /// 11 | /// Constructor without params to be used in COM Objects connections 12 | /// 13 | public AsciiQRCode() { } 14 | 15 | public AsciiQRCode(QRCodeData data) : base(data) { } 16 | 17 | 18 | /// 19 | /// Returns a strings that contains the resulting QR code as ASCII chars. 20 | /// 21 | /// Number of repeated darkColorString/whiteSpaceString per module. 22 | /// String for use as dark color modules. In case of string make sure whiteSpaceString has the same length. 23 | /// String for use as white modules (whitespace). In case of string make sure darkColorString has the same length. 24 | /// End of line separator. (Default: \n) 25 | /// 26 | public string GetGraphic(int repeatPerModule, string darkColorString = "██", string whiteSpaceString = " ", bool drawQuietZones = true, string endOfLine = "\n") 27 | { 28 | return string.Join(endOfLine, GetLineByLineGraphic(repeatPerModule, darkColorString, whiteSpaceString, drawQuietZones)); 29 | } 30 | 31 | 32 | /// 33 | /// Returns an array of strings that contains each line of the resulting QR code as ASCII chars. 34 | /// 35 | /// Number of repeated darkColorString/whiteSpaceString per module. 36 | /// String for use as dark color modules. In case of string make sure whiteSpaceString has the same length. 37 | /// String for use as white modules (whitespace). In case of string make sure darkColorString has the same length. 38 | /// 39 | public string[] GetLineByLineGraphic(int repeatPerModule, string darkColorString = "██", string whiteSpaceString = " ", bool drawQuietZones = true) 40 | { 41 | var qrCode = new List(); 42 | //We need to adjust the repeatPerModule based on number of characters in darkColorString 43 | //(we assume whiteSpaceString has the same number of characters) 44 | //to keep the QR code as square as possible. 45 | var quietZonesModifier = (drawQuietZones ? 0 : 8); 46 | var quietZonesOffset = (int)(quietZonesModifier * 0.5); 47 | var adjustmentValueForNumberOfCharacters = darkColorString.Length / 2 != 1 ? darkColorString.Length / 2 : 0; 48 | var verticalNumberOfRepeats = repeatPerModule + adjustmentValueForNumberOfCharacters; 49 | var sideLength = (QrCodeData.ModuleMatrix.Count - quietZonesModifier) * verticalNumberOfRepeats; 50 | for (var y = 0; y < sideLength; y++) 51 | { 52 | var lineBuilder = new StringBuilder(); 53 | for (var x = 0; x < QrCodeData.ModuleMatrix.Count - quietZonesModifier; x++) 54 | { 55 | var module = QrCodeData.ModuleMatrix[x + quietZonesOffset][((y + verticalNumberOfRepeats) / verticalNumberOfRepeats - 1)+quietZonesOffset]; 56 | for (var i = 0; i < repeatPerModule; i++) 57 | { 58 | lineBuilder.Append(module ? darkColorString : whiteSpaceString); 59 | } 60 | } 61 | qrCode.Add(lineBuilder.ToString()); 62 | } 63 | return qrCode.ToArray(); 64 | } 65 | } 66 | 67 | 68 | public static class AsciiQRCodeHelper 69 | { 70 | public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorString, string whiteSpaceString, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, string endOfLine = "\n", bool drawQuietZones = true) 71 | { 72 | using (var qrGenerator = new QRCodeGenerator()) 73 | using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) 74 | using (var qrCode = new AsciiQRCode(qrCodeData)) 75 | return qrCode.GetGraphic(pixelsPerModule, darkColorString, whiteSpaceString, drawQuietZones, endOfLine); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /QRCoderTests/PngByteQRCodeRendererTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using QRCoder; 3 | using Shouldly; 4 | using QRCoderTests.Helpers.XUnitExtenstions; 5 | using QRCoderTests.Helpers; 6 | 7 | namespace QRCoderTests 8 | { 9 | /**************************************************************************************************** 10 | * Note: Test cases compare the outcome visually even if it's slower than a byte-wise compare. 11 | * This is necessary, because the Deflate implementation differs on the different target 12 | * platforms and thus the outcome, even if visually identical, differs. Thus only a visual 13 | * test method makes sense. In addition bytewise differences shouldn't be important, if the 14 | * visual outcome is identical and thus the qr code is identical/scannable. 15 | ****************************************************************************************************/ 16 | public class PngByteQRCodeRendererTests 17 | { 18 | const string QRCodeContent = "This is a quick test! 123#?"; 19 | const string VisualTestPath = null; 20 | 21 | [Fact] 22 | [Category("QRRenderer/PngByteQRCode")] 23 | public void can_render_pngbyte_qrcode_blackwhite() 24 | { 25 | var pngCodeGfx = HelperFunctions.GeneratePng(QRCodeContent, pr => pr.GetGraphic(5)); 26 | HelperFunctions.TestByHash(pngCodeGfx, "90869fd365fe75e8aef3da40765dd5cc"); 27 | HelperFunctions.TestByDecode(pngCodeGfx, QRCodeContent); 28 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_pngbyte_qrcode_blackwhite), pngCodeGfx); 29 | } 30 | 31 | [Fact] 32 | [Category("QRRenderer/PngByteQRCode")] 33 | public void can_render_pngbyte_qrcode_color() 34 | { 35 | var pngCodeGfx = HelperFunctions.GeneratePng(QRCodeContent, pr => pr.GetGraphic(5, new byte[] { 255, 0, 0 }, new byte[] { 0, 0, 255 })); 36 | HelperFunctions.TestByHash(pngCodeGfx, "55093e9b9e39dc8368721cb535844425"); 37 | // HelperFunctions.TestByDecode(pngCodeGfx, QRCodeContent); => Not decodable 38 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_pngbyte_qrcode_color), pngCodeGfx); 39 | } 40 | 41 | 42 | [Fact] 43 | [Category("QRRenderer/PngByteQRCode")] 44 | public void can_render_pngbyte_qrcode_color_with_alpha() 45 | { 46 | var pngCodeGfx = HelperFunctions.GeneratePng(QRCodeContent, pr => pr.GetGraphic(5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 })); 47 | HelperFunctions.TestByHash(pngCodeGfx, "afc7674cb4849860cbf73684970e5332"); 48 | // HelperFunctions.TestByDecode(pngCodeGfx, QRCodeContent); => Not decodable 49 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_pngbyte_qrcode_color_with_alpha), pngCodeGfx); 50 | } 51 | 52 | [Fact] 53 | [Category("QRRenderer/PngByteQRCode")] 54 | public void can_render_pngbyte_qrcode_color_without_quietzones() 55 | { 56 | var pngCodeGfx = HelperFunctions.GeneratePng(QRCodeContent, pr => pr.GetGraphic(5, new byte[] { 255, 255, 255, 127 }, new byte[] { 0, 0, 255 }, false)); 57 | HelperFunctions.TestByHash(pngCodeGfx, "af60811deaa524e0d165baecdf40ab72"); 58 | // HelperFunctions.TestByDecode(pngCodeGfx, QRCodeContent); => not decodable 59 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_pngbyte_qrcode_color_without_quietzones), pngCodeGfx); 60 | } 61 | 62 | [Fact] 63 | [Category("QRRenderer/PngByteQRCode")] 64 | public void can_instantate_pngbyte_qrcode_parameterless() 65 | { 66 | var pngCode = new PngByteQRCode(); 67 | pngCode.ShouldNotBeNull(); 68 | pngCode.ShouldBeOfType(); 69 | } 70 | 71 | [Fact] 72 | [Category("QRRenderer/PngByteQRCode")] 73 | public void can_render_pngbyte_qrcode_from_helper() 74 | { 75 | //Create QR code 76 | var pngCodeGfx = PngByteQRCodeHelper.GetQRCode(QRCodeContent, QRCodeGenerator.ECCLevel.L, 10); 77 | HelperFunctions.TestByHash(pngCodeGfx, "e649d6a485873ac18b5aab791f325284"); 78 | HelperFunctions.TestByDecode(pngCodeGfx, QRCodeContent); 79 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_pngbyte_qrcode_from_helper), pngCodeGfx); 80 | } 81 | 82 | [Fact] 83 | [Category("QRRenderer/PngByteQRCode")] 84 | public void can_render_pngbyte_qrcode_from_helper_2() 85 | { 86 | //Create QR code 87 | 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); 88 | HelperFunctions.TestByHash(pngCodeGfx, "afc7674cb4849860cbf73684970e5332"); 89 | // HelperFunctions.TestByDecode(pngCodeGfx, QRCodeContent); => not decodable 90 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_pngbyte_qrcode_from_helper_2), pngCodeGfx); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /QRCoder/BitmapByteQRCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using static QRCoder.QRCodeGenerator; 5 | 6 | namespace QRCoder 7 | { 8 | 9 | // ReSharper disable once InconsistentNaming 10 | public class BitmapByteQRCode : AbstractQRCode, IDisposable 11 | { 12 | /// 13 | /// Constructor without params to be used in COM Objects connections 14 | /// 15 | public BitmapByteQRCode() { } 16 | 17 | public BitmapByteQRCode(QRCodeData data) : base(data) { } 18 | 19 | public byte[] GetGraphic(int pixelsPerModule) 20 | { 21 | return GetGraphic(pixelsPerModule, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0xFF, 0xFF, 0xFF }); 22 | } 23 | 24 | public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex) 25 | { 26 | return GetGraphic(pixelsPerModule, HexColorToByteArray(darkColorHtmlHex), HexColorToByteArray(lightColorHtmlHex)); 27 | } 28 | 29 | public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgb, byte[] lightColorRgb) 30 | { 31 | var sideLength = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule; 32 | 33 | var moduleDark = darkColorRgb.Reverse(); 34 | var moduleLight = lightColorRgb.Reverse(); 35 | 36 | List bmp = new List(); 37 | 38 | //header 39 | bmp.AddRange(new byte[] { 0x42, 0x4D, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00 }); 40 | 41 | //width 42 | bmp.AddRange(IntTo4Byte(sideLength)); 43 | //height 44 | bmp.AddRange(IntTo4Byte(sideLength)); 45 | 46 | //header end 47 | bmp.AddRange(new byte[] { 0x01, 0x00, 0x18, 0x00 }); 48 | 49 | //draw qr code 50 | for (var x = sideLength-1; x >= 0; x = x - pixelsPerModule) 51 | { 52 | for (int pm = 0; pm < pixelsPerModule; pm++) 53 | { 54 | for (var y = 0; y < sideLength; y = y + pixelsPerModule) 55 | { 56 | var module = 57 | this.QrCodeData.ModuleMatrix[(x + pixelsPerModule)/pixelsPerModule - 1][(y + pixelsPerModule)/pixelsPerModule - 1]; 58 | for (int i = 0; i < pixelsPerModule; i++) 59 | { 60 | bmp.AddRange(module ? moduleDark : moduleLight); 61 | } 62 | } 63 | if (sideLength%4 != 0) 64 | { 65 | for (int i = 0; i < sideLength%4; i++) 66 | { 67 | bmp.Add(0x00); 68 | } 69 | } 70 | } 71 | } 72 | 73 | //finalize with terminator 74 | bmp.AddRange(new byte[] { 0x00, 0x00 }); 75 | 76 | return bmp.ToArray(); 77 | } 78 | 79 | private byte[] HexColorToByteArray(string colorString) 80 | { 81 | if (colorString.StartsWith("#")) 82 | colorString = colorString.Substring(1); 83 | byte[] byteColor = new byte[colorString.Length / 2]; 84 | for (int i = 0; i < byteColor.Length; i++) 85 | byteColor[i] = byte.Parse(colorString.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture); 86 | return byteColor; 87 | } 88 | 89 | private byte[] IntTo4Byte(int inp) 90 | { 91 | byte[] bytes = new byte[2]; 92 | unchecked 93 | { 94 | bytes[1] = (byte)(inp >> 8); 95 | bytes[0] = (byte)(inp); 96 | } 97 | return bytes; 98 | } 99 | } 100 | 101 | 102 | public static class BitmapByteQRCodeHelper 103 | { 104 | public static byte[] GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex, 105 | string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, 106 | EciMode eciMode = EciMode.Default, int requestedVersion = -1) 107 | { 108 | using (var qrGenerator = new QRCodeGenerator()) 109 | using ( 110 | var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, 111 | requestedVersion)) 112 | using (var qrCode = new BitmapByteQRCode(qrCodeData)) 113 | return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex); 114 | } 115 | 116 | public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size) 117 | { 118 | using (var qrGen = new QRCodeGenerator()) 119 | using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) 120 | using (var qrBmp = new BitmapByteQRCode(qrCode)) 121 | return qrBmp.GetGraphic(size); 122 | 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /QRCoderTests/Helpers/HelperFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.IO; 4 | using System.Security.Cryptography; 5 | using SixLabors.ImageSharp; 6 | using SixLabors.ImageSharp.PixelFormats; 7 | using Shouldly; 8 | using QRCoder; 9 | using System.Reflection; 10 | 11 | namespace QRCoderTests.Helpers 12 | { 13 | public static class HelperFunctions 14 | { 15 | public static string BitmapToHash(Image img) 16 | { 17 | byte[] imgBytes = null; 18 | using (var ms = new MemoryStream()) 19 | { 20 | img.SaveAsPng(ms); 21 | imgBytes = ms.ToArray(); 22 | ms.Dispose(); 23 | } 24 | return ByteArrayToHash(imgBytes); 25 | } 26 | 27 | public static Image LoadAssetImage() => 28 | Image.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("QRCoderTests.assets.noun_software engineer_2909346.png")); 29 | 30 | public static string LoadAssetSvg() 31 | { 32 | using (var sr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream($"QRCoderTests.assets.noun_Scientist_2909361.svg"))) 33 | return sr.ReadToEnd(); 34 | } 35 | 36 | public static string ByteArrayToHash(byte[] data) 37 | { 38 | var md5 = MD5.Create(); 39 | var hash = md5.ComputeHash(data); 40 | return BitConverter.ToString(hash).Replace("-", "").ToLower(); 41 | } 42 | 43 | public static string StringToHash(string data) 44 | { 45 | return ByteArrayToHash(Encoding.UTF8.GetBytes(data)); 46 | } 47 | 48 | public static void TestByDecode(Image image, string desiredContent) 49 | { 50 | ZXing.ImageSharp.BarcodeReader reader = new ZXing.ImageSharp.BarcodeReader(); 51 | ZXing.Result result = reader.Decode(image); 52 | result.Text.ShouldBe(desiredContent); 53 | } 54 | 55 | public static void TestByDecode(byte[] pngCodeGfx, string desiredContent) 56 | { 57 | using (var mStream = new MemoryStream(pngCodeGfx)) 58 | { 59 | ZXing.Result result; 60 | 61 | Image image = Image.Load(mStream); 62 | Type pixelType = image.GetType().GetGenericArguments()[0]; 63 | if (pixelType == typeof(Rgba32)) 64 | { 65 | ZXing.ImageSharp.BarcodeReader reader = new ZXing.ImageSharp.BarcodeReader(); 66 | result = reader.Decode(image as Image); 67 | } 68 | else if (pixelType == typeof(L8)) 69 | { 70 | ZXing.ImageSharp.BarcodeReader reader = new ZXing.ImageSharp.BarcodeReader(); 71 | result = reader.Decode(image as Image); 72 | } 73 | else 74 | throw new NotImplementedException(pixelType.ToString()); 75 | result.Text.ShouldBe(desiredContent); 76 | } 77 | } 78 | 79 | public static void TestByHash(Image image, string desiredHash) => 80 | BitmapToHash(image).ShouldBe(desiredHash); 81 | 82 | public static void TestByHash(byte[] pngCodeGfx, string desiredHash) 83 | { 84 | using (var mStream = new MemoryStream(pngCodeGfx)) 85 | { 86 | var img = Image.Load(mStream); 87 | var result = BitmapToHash(img); 88 | result.ShouldBe(desiredHash); 89 | } 90 | } 91 | 92 | public static void TestByHash(string svg, string desiredHash) => 93 | ByteArrayToHash(UTF8Encoding.UTF8.GetBytes(desiredHash)); 94 | 95 | public static void TestImageToFile(string path, string testName, Image image) 96 | { 97 | if (String.IsNullOrEmpty(path)) 98 | return; 99 | 100 | image.Save(Path.Combine(path, $"qrtest_{testName}.png")); 101 | } 102 | 103 | public static void TestImageToFile(string path, string testName, byte[] data) 104 | { 105 | if (String.IsNullOrEmpty(path)) 106 | return; 107 | //Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346 108 | File.WriteAllBytes(Path.Combine(path, $"qrtestPNG_{testName}.png"), data); 109 | } 110 | 111 | public static void TestImageToFile(string path, string testName, string svg) 112 | { 113 | if (String.IsNullOrEmpty(path)) 114 | return; 115 | 116 | //Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346 117 | File.WriteAllText(Path.Combine(path, $"qrtestSVG_{testName}.svg"), svg); 118 | } 119 | 120 | public static Image GenerateImage(string content, Func> getGraphic) 121 | { 122 | QRCodeGenerator gen = new QRCodeGenerator(); 123 | QRCodeData data = gen.CreateQrCode(content, QRCodeGenerator.ECCLevel.H); 124 | return getGraphic(new QRCode(data)); 125 | } 126 | 127 | public static byte[] GeneratePng(string content, Func getGraphic) 128 | { 129 | QRCodeGenerator gen = new QRCodeGenerator(); 130 | QRCodeData data = gen.CreateQrCode(content, QRCodeGenerator.ECCLevel.L); 131 | return getGraphic(new PngByteQRCode(data)); 132 | } 133 | 134 | public static string GenerateSvg(string content, Func getGraphic) 135 | { 136 | QRCodeGenerator gen = new QRCodeGenerator(); 137 | QRCodeData data = gen.CreateQrCode(content, QRCodeGenerator.ECCLevel.H); 138 | return getGraphic(new SvgQRCode(data)); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /QRCoder/PostscriptQRCode.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using SixLabors.ImageSharp.PixelFormats; 3 | using System; 4 | using static QRCoder.QRCodeGenerator; 5 | 6 | namespace QRCoder 7 | { 8 | public class PostscriptQRCode : AbstractQRCode, IDisposable 9 | { 10 | /// 11 | /// Constructor without params to be used in COM Objects connections 12 | /// 13 | public PostscriptQRCode() { } 14 | public PostscriptQRCode(QRCodeData data) : base(data) { } 15 | 16 | public string GetGraphic(int pointsPerModule, bool epsFormat = false) 17 | { 18 | var viewBox = new Size(pointsPerModule * this.QrCodeData.ModuleMatrix.Count, pointsPerModule * this.QrCodeData.ModuleMatrix.Count); 19 | return this.GetGraphic(viewBox, Color.Black, Color.White, true, epsFormat); 20 | } 21 | public string GetGraphic(int pointsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, bool epsFormat = false) 22 | { 23 | var viewBox = new Size(pointsPerModule * this.QrCodeData.ModuleMatrix.Count, pointsPerModule * this.QrCodeData.ModuleMatrix.Count); 24 | return this.GetGraphic(viewBox, darkColor, lightColor, drawQuietZones, epsFormat); 25 | } 26 | 27 | public string GetGraphic(int pointsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, bool epsFormat = false) 28 | { 29 | var viewBox = new Size(pointsPerModule * this.QrCodeData.ModuleMatrix.Count, pointsPerModule * this.QrCodeData.ModuleMatrix.Count); 30 | return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, epsFormat); 31 | } 32 | 33 | public string GetGraphic(Size viewBox, bool drawQuietZones = true, bool epsFormat = false) 34 | { 35 | return this.GetGraphic(viewBox, Color.Black, Color.White, drawQuietZones, epsFormat); 36 | } 37 | 38 | public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, bool epsFormat = false) 39 | { 40 | return this.GetGraphic(viewBox, Color.Parse(darkColorHex), Color.Parse(lightColorHex), drawQuietZones, epsFormat); 41 | } 42 | 43 | public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, bool epsFormat = false) 44 | { 45 | var offset = drawQuietZones ? 0 : 4; 46 | var drawableModulesCount = this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : offset * 2); 47 | var pointsPerModule = (double)Math.Min(viewBox.Width, viewBox.Height) / (double)drawableModulesCount; 48 | 49 | string psFile = string.Format(psHeader, new object[] { 50 | DateTime.Now.ToString("s"), CleanSvgVal(viewBox.Width), CleanSvgVal(pointsPerModule), 51 | epsFormat ? "EPSF-3.0" : string.Empty 52 | }); 53 | psFile += string.Format(psFunctions, new object[] { 54 | CleanSvgVal(darkColor.ToPixel().R /255.0), CleanSvgVal(darkColor.ToPixel().G /255.0), CleanSvgVal(darkColor.ToPixel().B /255.0), 55 | CleanSvgVal(lightColor.ToPixel().R /255.0), CleanSvgVal(lightColor.ToPixel().G /255.0), CleanSvgVal(lightColor.ToPixel().B /255.0), 56 | drawableModulesCount 57 | }); 58 | 59 | for (int xi = offset; xi < offset + drawableModulesCount; xi++) 60 | { 61 | if (xi > offset) 62 | psFile += "nl\n"; 63 | for (int yi = offset; yi < offset + drawableModulesCount; yi++) 64 | { 65 | psFile += (this.QrCodeData.ModuleMatrix[xi][yi] ? "f " : "b "); 66 | } 67 | psFile += "\n"; 68 | } 69 | return psFile + psFooter; 70 | } 71 | 72 | private string CleanSvgVal(double input) 73 | { 74 | //Clean double values for international use/formats 75 | return input.ToString(System.Globalization.CultureInfo.InvariantCulture); 76 | } 77 | 78 | private const string psHeader = @"%!PS-Adobe-3.0 {3} 79 | %%Creator: QRCoder.NET 80 | %%Title: QRCode 81 | %%CreationDate: {0} 82 | %%DocumentData: Clean7Bit 83 | %%Origin: 0 84 | %%DocumentMedia: Default {1} {1} 0 () () 85 | %%BoundingBox: 0 0 {1} {1} 86 | %%LanguageLevel: 2 87 | %%Pages: 1 88 | %%Page: 1 1 89 | %%EndComments 90 | %%BeginConstants 91 | /sz {1} def 92 | /sc {2} def 93 | %%EndConstants 94 | %%BeginFeature: *PageSize Default 95 | << /PageSize [ sz sz ] /ImagingBBox null >> setpagedevice 96 | %%EndFeature 97 | "; 98 | 99 | private const string psFunctions = @"%%BeginFunctions 100 | /csquare {{ 101 | newpath 102 | 0 0 moveto 103 | 0 1 rlineto 104 | 1 0 rlineto 105 | 0 -1 rlineto 106 | closepath 107 | setrgbcolor 108 | fill 109 | }} def 110 | /f {{ 111 | {0} {1} {2} csquare 112 | 1 0 translate 113 | }} def 114 | /b {{ 115 | 1 0 translate 116 | }} def 117 | /background {{ 118 | {3} {4} {5} csquare 119 | }} def 120 | /nl {{ 121 | -{6} -1 translate 122 | }} def 123 | %%EndFunctions 124 | %%BeginBody 125 | 0 0 moveto 126 | gsave 127 | sz sz scale 128 | background 129 | grestore 130 | gsave 131 | sc sc scale 132 | 0 {6} 1 sub translate 133 | "; 134 | 135 | private const string psFooter = @"%%EndBody 136 | grestore 137 | showpage 138 | %%EOF 139 | "; 140 | } 141 | 142 | public static class PostscriptQRCodeHelper 143 | { 144 | public static string GetQRCode(string plainText, int pointsPerModule, string darkColorHex, string lightColorHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, bool epsFormat = false) 145 | { 146 | using (var qrGenerator = new QRCodeGenerator()) 147 | using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) 148 | using (var qrCode = new PostscriptQRCode(qrCodeData)) 149 | return qrCode.GetGraphic(pointsPerModule, darkColorHex, lightColorHex, drawQuietZones, epsFormat); 150 | } 151 | } 152 | } -------------------------------------------------------------------------------- /QRCoderTests/QRCodeRendererTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using QRCoder; 3 | using Shouldly; 4 | using QRCoderTests.Helpers.XUnitExtenstions; 5 | using QRCoderTests.Helpers; 6 | using SixLabors.ImageSharp; 7 | using SixLabors.ImageSharp.PixelFormats; 8 | 9 | namespace QRCoderTests 10 | { 11 | public class QRCodeRendererTests 12 | { 13 | const string QRCodeContent = "This is a quick test! 123#?"; 14 | const string VisualTestPath = null; 15 | 16 | [Fact] 17 | [Category("QRRenderer/QRCode")] 18 | public void can_create_qrcode_standard_graphic() 19 | { 20 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10) as Image); 21 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_standard_graphic), image); 22 | HelperFunctions.TestByDecode(image, QRCodeContent); 23 | HelperFunctions.TestByHash(image, "c0f8af4256eddc7e566983e539cce389"); 24 | } 25 | 26 | [Fact] 27 | [Category("QRRenderer/QRCode")] 28 | public void can_create_qrcode_standard_graphic_hex() 29 | { 30 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10, "#000000", "#ffffff") as Image); 31 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_standard_graphic_hex), image); 32 | HelperFunctions.TestByDecode(image, QRCodeContent); 33 | HelperFunctions.TestByHash(image, "c0f8af4256eddc7e566983e539cce389"); 34 | } 35 | 36 | [Fact] 37 | [Category("QRRenderer/QRCode")] 38 | public void can_create_qrcode_standard_graphic_without_quietzones() 39 | { 40 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(5, Color.Black, Color.White, false) as Image); 41 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_standard_graphic_without_quietzones), image); 42 | HelperFunctions.TestByDecode(image, QRCodeContent); 43 | HelperFunctions.TestByHash(image, "8a2d62fa98c09d764a21466b8d6bb6c8"); 44 | } 45 | 46 | [Fact] 47 | [Category("QRRenderer/QRCode")] 48 | public void can_create_qrcode_with_transparent_logo_graphic() 49 | { 50 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10, Color.Black, Color.Transparent, icon: HelperFunctions.LoadAssetImage()) as Image); 51 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_with_transparent_logo_graphic), image); 52 | HelperFunctions.TestByDecode(image, QRCodeContent); 53 | HelperFunctions.TestByHash(image, "d19c708b8e2b28c62a6b9db3e630179a"); 54 | } 55 | 56 | [Fact] 57 | [Category("QRRenderer/QRCode")] 58 | public void can_create_qrcode_with_non_transparent_logo_graphic() 59 | { 60 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10, Color.Black, Color.White, icon: HelperFunctions.LoadAssetImage()) as Image); 61 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_with_non_transparent_logo_graphic), image); 62 | HelperFunctions.TestByDecode(image, QRCodeContent); 63 | HelperFunctions.TestByHash(image, "5e535aac60c1bc7ee8ec506916cd2dd8"); 64 | } 65 | 66 | [Fact] 67 | [Category("QRRenderer/QRCode")] 68 | public void can_create_qrcode_with_logo_and_with_transparent_border() 69 | { 70 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10, Color.Black, Color.Transparent, iconBorderWidth: 6, icon: HelperFunctions.LoadAssetImage()) as Image); 71 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_with_logo_and_with_transparent_border), image); 72 | HelperFunctions.TestByDecode(image, QRCodeContent); 73 | HelperFunctions.TestByHash(image, "d19c708b8e2b28c62a6b9db3e630179a"); 74 | } 75 | 76 | [Fact] 77 | [Category("QRRenderer/QRCode")] 78 | public void can_create_qrcode_with_logo_and_with_standard_border() 79 | { 80 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10, Color.Black, Color.White, iconBorderWidth: 6, icon: HelperFunctions.LoadAssetImage()) as Image); 81 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_with_logo_and_with_standard_border), image); 82 | HelperFunctions.TestByDecode(image, QRCodeContent); 83 | HelperFunctions.TestByHash(image, "91f35d10164ccd4a9ad621e2dc81c86b"); 84 | } 85 | 86 | [Fact] 87 | [Category("QRRenderer/QRCode")] 88 | public void can_create_qrcode_with_logo_and_with_custom_border() 89 | { 90 | var image = HelperFunctions.GenerateImage(QRCodeContent, (qr) => qr.GetGraphic(10, Color.Black, Color.Transparent, iconBorderWidth: 6, iconBackgroundColor: Color.DarkGreen, icon: HelperFunctions.LoadAssetImage()) as Image); 91 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_create_qrcode_with_logo_and_with_custom_border), image); 92 | HelperFunctions.TestByDecode(image, QRCodeContent); 93 | HelperFunctions.TestByHash(image, "ce13cc3372aa477a914c9828cdad4754"); 94 | } 95 | 96 | 97 | [Fact] 98 | [Category("QRRenderer/QRCode")] 99 | public void can_instantate_qrcode_parameterless() 100 | { 101 | var svgCode = new QRCode(); 102 | svgCode.ShouldNotBeNull(); 103 | svgCode.ShouldBeOfType(); 104 | } 105 | 106 | [Fact] 107 | [Category("QRRenderer/QRCode")] 108 | public void can_render_qrcode_from_helper() 109 | { 110 | var image = QRCodeHelper.GetQRCode(QRCodeContent, 10, Color.Black, Color.White, QRCodeGenerator.ECCLevel.H) as Image; 111 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_qrcode_from_helper), image); 112 | HelperFunctions.TestByDecode(image, QRCodeContent); 113 | HelperFunctions.TestByHash(image, "c0f8af4256eddc7e566983e539cce389"); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /QRCoderTests/SvgQRCodeRendererTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using QRCoder; 3 | using Shouldly; 4 | using QRCoderTests.Helpers.XUnitExtenstions; 5 | using QRCoderTests.Helpers; 6 | using SixLabors.ImageSharp; 7 | 8 | namespace QRCoderTests 9 | { 10 | 11 | public class SvgQRCodeRendererTests 12 | { 13 | const string QRCodeContent = "This is a quick test! 123#?"; 14 | const string VisualTestPath = null; 15 | 16 | [Fact] 17 | [Category("QRRenderer/SvgQRCode")] 18 | public void can_render_svg_qrcode_simple() 19 | { 20 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(5)); 21 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_simple), svg); 22 | HelperFunctions.TestByHash(svg, "5c251275a435a9aed7e591eb9c2e9949"); 23 | } 24 | 25 | [Fact] 26 | [Category("QRRenderer/SvgQRCode")] 27 | public void can_render_svg_qrcode() 28 | { 29 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(10, Color.Red, Color.White)); 30 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode), svg); 31 | HelperFunctions.TestByHash(svg, "1baa8c6ac3bd8c1eabcd2c5422dd9f78"); 32 | } 33 | 34 | [Fact] 35 | [Category("QRRenderer/SvgQRCode")] 36 | public void can_render_svg_qrcode_viewbox_mode() 37 | { 38 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(new Size(128, 128))); 39 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_viewbox_mode), svg); 40 | HelperFunctions.TestByHash(svg, "56719c7db39937c74377855a5dc4af0a"); 41 | } 42 | 43 | [Fact] 44 | [Category("QRRenderer/SvgQRCode")] 45 | public void can_render_svg_qrcode_viewbox_mode_viewboxattr() 46 | { 47 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(new Size(128, 128), sizingMode: SvgQRCode.SizingMode.ViewBoxAttribute)); 48 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_viewbox_mode_viewboxattr), svg); 49 | HelperFunctions.TestByHash(svg, "788afdb693b0b71eed344e495c180b60"); 50 | } 51 | 52 | [Fact] 53 | [Category("QRRenderer/SvgQRCode")] 54 | public void can_render_svg_qrcode_without_quietzones() 55 | { 56 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(10, Color.Red, Color.White, false)); 57 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_without_quietzones), svg); 58 | HelperFunctions.TestByHash(svg, "2a582427d86b51504c08ebcbcf0472bd"); 59 | } 60 | 61 | [Fact] 62 | [Category("QRRenderer/SvgQRCode")] 63 | public void can_render_svg_qrcode_without_quietzones_hex() 64 | { 65 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(10, "#000000", "#ffffff", false)); 66 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_without_quietzones_hex), svg); 67 | HelperFunctions.TestByHash(svg, "4ab0417cc6127e347ca1b2322c49ed7d"); 68 | } 69 | 70 | [Fact] 71 | [Category("QRRenderer/SvgQRCode")] 72 | public void can_render_svg_qrcode_with_png_logo() 73 | { 74 | var logoBitmap = HelperFunctions.LoadAssetImage(); 75 | var logoObj = new SvgQRCode.SvgLogo(iconRasterized: logoBitmap, 15); 76 | logoObj.GetMediaType().ShouldBe(SvgQRCode.SvgLogo.MediaType.PNG); 77 | 78 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj)); 79 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_with_png_logo), svg); 80 | HelperFunctions.TestByHash(svg, "78e02e8ba415f15817d5ed88c4afca31"); 81 | } 82 | 83 | [Fact] 84 | [Category("QRRenderer/SvgQRCode")] 85 | public void can_render_svg_qrcode_with_svg_logo_embedded() 86 | { 87 | var logoSvg = HelperFunctions.LoadAssetSvg(); 88 | var logoObj = new SvgQRCode.SvgLogo(logoSvg, 20); 89 | logoObj.GetMediaType().ShouldBe(SvgQRCode.SvgLogo.MediaType.SVG); 90 | 91 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj)); 92 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_with_svg_logo_embedded), svg); 93 | HelperFunctions.TestByHash(svg, "855eb988d3af035abd273ed1629aa952"); 94 | 95 | } 96 | 97 | [Fact] 98 | [Category("QRRenderer/SvgQRCode")] 99 | public void can_render_svg_qrcode_with_svg_logo_image_tag() 100 | { 101 | var logoSvg = HelperFunctions.LoadAssetSvg(); 102 | var logoObj = new SvgQRCode.SvgLogo(logoSvg, 20, iconEmbedded: false); 103 | 104 | var svg = HelperFunctions.GenerateSvg(QRCodeContent, sg => sg.GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj)); 105 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_with_svg_logo_image_tag), svg); 106 | HelperFunctions.TestByHash(svg, "bd442ea77d45a41a4f490b8d41591e04"); 107 | } 108 | 109 | [Fact] 110 | [Category("QRRenderer/SvgQRCode")] 111 | public void can_instantate_parameterless() 112 | { 113 | var svgCode = new SvgQRCode(); 114 | svgCode.ShouldNotBeNull(); 115 | svgCode.ShouldBeOfType(); 116 | } 117 | 118 | [Fact] 119 | [Category("QRRenderer/SvgQRCode")] 120 | public void can_render_svg_qrcode_from_helper() 121 | { 122 | //Create QR code 123 | var svg = SvgQRCodeHelper.GetQRCode("A", 2, "#000000", "#ffffff", QRCodeGenerator.ECCLevel.Q); 124 | HelperFunctions.TestImageToFile(VisualTestPath, nameof(can_render_svg_qrcode_from_helper), svg); 125 | HelperFunctions.TestByHash(svg, "f5ec37aa9fb207e3701cc0d86c4a357d"); 126 | } 127 | } 128 | } 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /QRCoder/QRCodeData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace QRCoder 6 | { 7 | using System; 8 | using System.IO; 9 | using System.IO.Compression; 10 | 11 | public class QRCodeData : IDisposable 12 | { 13 | public List ModuleMatrix { get; set; } 14 | 15 | public QRCodeData(int version) 16 | { 17 | this.Version = version; 18 | var size = ModulesPerSideFromVersion(version); 19 | this.ModuleMatrix = new List(); 20 | for (var i = 0; i < size; i++) 21 | this.ModuleMatrix.Add(new BitArray(size)); 22 | } 23 | public QRCodeData(string pathToRawData, Compression compressMode) : this(File.ReadAllBytes(pathToRawData), compressMode) 24 | { 25 | } 26 | 27 | public QRCodeData(byte[] rawData, Compression compressMode) 28 | { 29 | var bytes = new List(rawData); 30 | 31 | //Decompress 32 | if (compressMode == Compression.Deflate) 33 | { 34 | using (var input = new MemoryStream(bytes.ToArray())) 35 | { 36 | using (var output = new MemoryStream()) 37 | { 38 | using (var dstream = new DeflateStream(input, CompressionMode.Decompress)) 39 | dstream.CopyTo(output); 40 | bytes = new List(output.ToArray()); 41 | } 42 | } 43 | } 44 | else if (compressMode == Compression.GZip) 45 | { 46 | using (var input = new MemoryStream(bytes.ToArray())) 47 | { 48 | using (var output = new MemoryStream()) 49 | { 50 | using (var dstream = new GZipStream(input, CompressionMode.Decompress)) 51 | dstream.CopyTo(output); 52 | bytes = new List(output.ToArray()); 53 | } 54 | } 55 | } 56 | 57 | if (bytes[0] != 0x51 || bytes[1] != 0x52 || bytes[2] != 0x52) 58 | throw new Exception("Invalid raw data file. Filetype doesn't match \"QRR\"."); 59 | 60 | //Set QR code version 61 | var sideLen = (int)bytes[4]; 62 | bytes.RemoveRange(0, 5); 63 | this.Version = (sideLen - 21 - 8) / 4 + 1; 64 | 65 | //Unpack 66 | var modules = new Queue(8 * bytes.Count); 67 | foreach (var b in bytes) 68 | { 69 | var bArr = new BitArray(new byte[] { b }); 70 | for (int i = 7; i >= 0; i--) 71 | { 72 | modules.Enqueue((b & (1 << i)) != 0); 73 | } 74 | } 75 | 76 | //Build module matrix 77 | this.ModuleMatrix = new List(sideLen); 78 | for (int y = 0; y < sideLen; y++) 79 | { 80 | this.ModuleMatrix.Add(new BitArray(sideLen)); 81 | for (int x = 0; x < sideLen; x++) 82 | { 83 | this.ModuleMatrix[y][x] = modules.Dequeue(); 84 | } 85 | } 86 | 87 | } 88 | 89 | public byte[] GetRawData(Compression compressMode) 90 | { 91 | var bytes = new List(); 92 | 93 | //Add header - signature ("QRR") 94 | bytes.AddRange(new byte[]{ 0x51, 0x52, 0x52, 0x00 }); 95 | 96 | //Add header - rowsize 97 | bytes.Add((byte)ModuleMatrix.Count); 98 | 99 | //Build data queue 100 | var dataQueue = new Queue(); 101 | foreach (var row in ModuleMatrix) 102 | { 103 | foreach (var module in row) 104 | { 105 | dataQueue.Enqueue((bool)module ? 1 : 0); 106 | } 107 | } 108 | for (int i = 0; i < 8 - (ModuleMatrix.Count * ModuleMatrix.Count) % 8; i++) 109 | { 110 | dataQueue.Enqueue(0); 111 | } 112 | 113 | //Process queue 114 | while (dataQueue.Count > 0) 115 | { 116 | byte b = 0; 117 | for (int i = 7; i >= 0; i--) 118 | { 119 | b += (byte)(dataQueue.Dequeue() << i); 120 | } 121 | bytes.Add(b); 122 | } 123 | var rawData = bytes.ToArray(); 124 | 125 | //Compress stream (optional) 126 | if (compressMode == Compression.Deflate) 127 | { 128 | using (var output = new MemoryStream()) 129 | { 130 | using (var dstream = new DeflateStream(output, CompressionMode.Compress)) 131 | { 132 | dstream.Write(rawData, 0, rawData.Length); 133 | } 134 | rawData = output.ToArray(); 135 | } 136 | } 137 | else if (compressMode == Compression.GZip) 138 | { 139 | using (var output = new MemoryStream()) 140 | { 141 | using (GZipStream gzipStream = new GZipStream(output, CompressionMode.Compress, true)) 142 | { 143 | gzipStream.Write(rawData, 0, rawData.Length); 144 | } 145 | rawData = output.ToArray(); 146 | } 147 | } 148 | return rawData; 149 | } 150 | 151 | public void SaveRawData(string filePath, Compression compressMode) 152 | { 153 | File.WriteAllBytes(filePath, GetRawData(compressMode)); 154 | } 155 | 156 | public int Version { get; private set; } 157 | 158 | private static int ModulesPerSideFromVersion(int version) 159 | { 160 | return 21 + (version - 1) * 4; 161 | } 162 | 163 | public void Dispose() 164 | { 165 | this.ModuleMatrix = null; 166 | this.Version = 0; 167 | 168 | } 169 | 170 | public enum Compression 171 | { 172 | Uncompressed, 173 | Deflate, 174 | GZip 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /QRCoder/ArtQRCode.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using System; 3 | using static QRCoder.ArtQRCode; 4 | using static QRCoder.QRCodeGenerator; 5 | 6 | // pull request raised to extend library used. 7 | namespace QRCoder 8 | { 9 | public class ArtQRCode : AbstractQRCode, IDisposable 10 | { 11 | /// 12 | /// Constructor without params to be used in COM Objects connections 13 | /// 14 | public ArtQRCode() => throw new NotImplementedException(); 15 | 16 | /// 17 | /// Creates new ArtQrCode object 18 | /// 19 | /// QRCodeData generated by the QRCodeGenerator 20 | public ArtQRCode(QRCodeData data) : base(data) { } 21 | 22 | /// 23 | /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true) 24 | /// 25 | /// Amount of px each dark/light module of the QR code shall take place in the final QR code image 26 | /// QRCode graphic as bitmap 27 | public Image GetGraphic(int pixelsPerModule) => throw new NotImplementedException(); 28 | 29 | /// 30 | /// 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) 31 | /// 32 | /// A bitmap object that will be used as background picture 33 | /// QRCode graphic as bitmap 34 | public Image GetGraphic(Image backgroundImage = null) => throw new NotImplementedException(); 35 | 36 | /// 37 | /// Renders an art-style QR code with dots as modules and various user settings 38 | /// 39 | /// Amount of px each dark/light module of the QR code shall take place in the final QR code image 40 | /// Color of the dark modules 41 | /// Color of the light modules 42 | /// Color of the background 43 | /// A bitmap object that will be used as background picture 44 | /// 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. 45 | /// If true a white border is drawn around the whole QR Code 46 | /// Style of the quiet zones 47 | /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone 48 | /// Optional image that should be used instead of the default finder patterns 49 | /// QRCode graphic as bitmap 50 | public Image GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Image backgroundImage = null, double pixelSizeFactor = 0.8, 51 | bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted, 52 | BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Image finderPatternImage = null) => throw new NotImplementedException(); 53 | 54 | 55 | /// 56 | /// Defines how the quiet zones shall be rendered. 57 | /// 58 | public enum QuietZoneStyle 59 | { 60 | Dotted, 61 | Flat 62 | } 63 | 64 | /// 65 | /// Defines how the background image (if set) shall be rendered. 66 | /// 67 | public enum BackgroundImageStyle 68 | { 69 | Fill, 70 | DataAreaOnly 71 | } 72 | } 73 | 74 | public static class ArtQRCodeHelper 75 | { 76 | /// 77 | /// Helper function to create an ArtQRCode graphic with a single function call 78 | /// 79 | /// Text/payload to be encoded inside the QR code 80 | /// Amount of px each dark/light module of the QR code shall take place in the final QR code image 81 | /// Color of the dark modules 82 | /// Color of the light modules 83 | /// Color of the background 84 | /// The level of error correction data 85 | /// Shall the generator be forced to work in UTF-8 mode? 86 | /// Should the byte-order-mark be used? 87 | /// Which ECI mode shall be used? 88 | /// Set fixed QR code target version. 89 | /// A bitmap object that will be used as background picture 90 | /// 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. 91 | /// If true a white border is drawn around the whole QR Code 92 | /// Style of the quiet zones 93 | /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone 94 | /// Optional image that should be used instead of the default finder patterns 95 | /// QRCode graphic as bitmap 96 | public static Image GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false, 97 | bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Image backgroundImage = null, double pixelSizeFactor = 0.8, 98 | bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat, 99 | BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Image finderPatternImage = null) => throw new NotImplementedException(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /QRCoder/PdfByteQRCode.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Globalization; 5 | using System.IO; 6 | using System.Linq; 7 | using static QRCoder.QRCodeGenerator; 8 | 9 | /* This renderer is inspired by RemusVasii: https://github.com/codebude/QRCoder/issues/223 */ 10 | namespace QRCoder 11 | { 12 | 13 | // ReSharper disable once InconsistentNaming 14 | public class PdfByteQRCode : AbstractQRCode, IDisposable 15 | { 16 | private readonly byte[] pdfBinaryComment = new byte[] { 0x25, 0xe2, 0xe3, 0xcf, 0xd3 }; 17 | 18 | /// 19 | /// Constructor without params to be used in COM Objects connections 20 | /// 21 | public PdfByteQRCode() { } 22 | 23 | public PdfByteQRCode(QRCodeData data) : base(data) { } 24 | 25 | /// 26 | /// Creates a PDF document with a black & white QR code 27 | /// 28 | /// 29 | /// 30 | public byte[] GetGraphic(int pixelsPerModule) 31 | { 32 | return GetGraphic(pixelsPerModule, "#000000", "#ffffff"); 33 | } 34 | 35 | /// 36 | /// Takes hexadecimal color string #000000 and returns byte[]{ 0, 0, 0 } 37 | /// 38 | /// Color in HEX format like #ffffff 39 | /// 40 | private byte[] HexColorToByteArray(string colorString) 41 | { 42 | if (colorString.StartsWith("#")) 43 | colorString = colorString.Substring(1); 44 | byte[] byteColor = new byte[colorString.Length / 2]; 45 | for (int i = 0; i < byteColor.Length; i++) 46 | byteColor[i] = byte.Parse(colorString.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); 47 | return byteColor; 48 | } 49 | 50 | /// 51 | /// Creates a PDF document with given colors DPI and quality 52 | /// 53 | /// 54 | /// 55 | /// 56 | /// 57 | /// 58 | /// 59 | public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, int dpi = 150, long jpgQuality = 85) 60 | { 61 | byte[] jpgArray = null; 62 | var imgSize = QrCodeData.ModuleMatrix.Count * pixelsPerModule; 63 | var pdfMediaSize = (imgSize * 72 / dpi).ToString(CultureInfo.InvariantCulture); 64 | 65 | // Get QR code image 66 | QRCode qr = new QRCode(QrCodeData); 67 | Image img = qr.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex); 68 | 69 | // Transform to JPG 70 | using (var ms = new MemoryStream()) 71 | { 72 | img.SaveAsJpeg(ms); 73 | jpgArray = ms.ToArray(); 74 | } 75 | 76 | //Create PDF document 77 | using (var stream = new MemoryStream()) 78 | { 79 | var writer = new StreamWriter(stream, System.Text.Encoding.GetEncoding("ASCII")); 80 | 81 | var xrefs = new List(); 82 | 83 | writer.Write("%PDF-1.5\r\n"); 84 | writer.Flush(); 85 | 86 | stream.Write(pdfBinaryComment, 0, pdfBinaryComment.Length); 87 | writer.WriteLine(); 88 | 89 | writer.Flush(); 90 | xrefs.Add(stream.Position); 91 | 92 | writer.Write( 93 | xrefs.Count.ToString() + " 0 obj\r\n" + 94 | "<<\r\n" + 95 | "/Type /Catalog\r\n" + 96 | "/Pages 2 0 R\r\n" + 97 | ">>\r\n" + 98 | "endobj\r\n" 99 | ); 100 | 101 | writer.Flush(); 102 | xrefs.Add(stream.Position); 103 | 104 | writer.Write( 105 | xrefs.Count.ToString() + " 0 obj\r\n" + 106 | "<<\r\n" + 107 | "/Count 1\r\n" + 108 | "/Kids [ <<\r\n" + 109 | "/Type /Page\r\n" + 110 | "/Parent 2 0 R\r\n" + 111 | "/MediaBox [0 0 " + pdfMediaSize + " " + pdfMediaSize + "]\r\n" + 112 | "/Resources << /ProcSet [ /PDF /ImageC ]\r\n" + 113 | "/XObject << /Im1 4 0 R >> >>\r\n" + 114 | "/Contents 3 0 R\r\n" + 115 | ">> ]\r\n" + 116 | ">>\r\n" + 117 | "endobj\r\n" 118 | ); 119 | 120 | var X = "q\r\n" + 121 | pdfMediaSize + " 0 0 " + pdfMediaSize + " 0 0 cm\r\n" + 122 | "/Im1 Do\r\n" + 123 | "Q"; 124 | 125 | writer.Flush(); 126 | xrefs.Add(stream.Position); 127 | 128 | writer.Write( 129 | xrefs.Count.ToString() + " 0 obj\r\n" + 130 | "<< /Length " + X.Length.ToString() + " >>\r\n" + 131 | "stream\r\n" + 132 | X + "endstream\r\n" + 133 | "endobj\r\n" 134 | ); 135 | 136 | writer.Flush(); 137 | xrefs.Add(stream.Position); 138 | 139 | writer.Write( 140 | xrefs.Count.ToString() + " 0 obj\r\n" + 141 | "<<\r\n" + 142 | "/Name /Im1\r\n" + 143 | "/Type /XObject\r\n" + 144 | "/Subtype /Image\r\n" + 145 | "/Width " + imgSize.ToString() + "/Height " + imgSize.ToString() + "/Length 5 0 R\r\n" + 146 | "/Filter /DCTDecode\r\n" + 147 | "/ColorSpace /DeviceRGB\r\n" + 148 | "/BitsPerComponent 8\r\n" + 149 | ">>\r\n" + 150 | "stream\r\n" 151 | ); 152 | writer.Flush(); 153 | stream.Write(jpgArray, 0, jpgArray.Length); 154 | writer.Write( 155 | "\r\n" + 156 | "endstream\r\n" + 157 | "endobj\r\n" 158 | ); 159 | 160 | writer.Flush(); 161 | xrefs.Add(stream.Position); 162 | 163 | writer.Write( 164 | xrefs.Count.ToString() + " 0 obj\r\n" + 165 | jpgArray.Length.ToString() + " endobj\r\n" 166 | ); 167 | 168 | writer.Flush(); 169 | var startxref = stream.Position; 170 | 171 | writer.Write( 172 | "xref\r\n" + 173 | "0 " + (xrefs.Count + 1).ToString() + "\r\n" + 174 | "0000000000 65535 f\r\n" 175 | ); 176 | 177 | foreach (var refValue in xrefs) 178 | writer.Write(refValue.ToString("0000000000") + " 00000 n\r\n"); 179 | 180 | writer.Write( 181 | "trailer\r\n" + 182 | "<<\r\n" + 183 | "/Size " + (xrefs.Count + 1).ToString() + "\r\n" + 184 | "/Root 1 0 R\r\n" + 185 | ">>\r\n" + 186 | "startxref\r\n" + 187 | startxref.ToString() + "\r\n" + 188 | "%%EOF" 189 | ); 190 | 191 | writer.Flush(); 192 | 193 | stream.Position = 0; 194 | 195 | return stream.ToArray(); 196 | } 197 | } 198 | } 199 | 200 | public static class PdfByteQRCodeHelper 201 | { 202 | public static byte[] GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex, 203 | string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, 204 | EciMode eciMode = EciMode.Default, int requestedVersion = -1) 205 | { 206 | using (var qrGenerator = new QRCodeGenerator()) 207 | using ( 208 | var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, 209 | requestedVersion)) 210 | using (var qrCode = new PdfByteQRCode(qrCodeData)) 211 | return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex); 212 | } 213 | 214 | public static byte[] GetQRCode(string txt, ECCLevel eccLevel, int size) 215 | { 216 | using (var qrGen = new QRCodeGenerator()) 217 | using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) 218 | using (var qrBmp = new PdfByteQRCode(qrCode)) 219 | return qrBmp.GetGraphic(size); 220 | 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /QRCoder/QRCode.cs: -------------------------------------------------------------------------------- 1 | using SixLabors.ImageSharp; 2 | using SixLabors.ImageSharp.PixelFormats; 3 | using SixLabors.ImageSharp.Processing; 4 | using System; 5 | using static QRCoder.QRCodeGenerator; 6 | 7 | namespace QRCoder 8 | { 9 | public class QRCode : AbstractQRCode, IDisposable 10 | { 11 | /// 12 | /// Constructor without params to be used in COM Objects connections 13 | /// 14 | public QRCode() { } 15 | 16 | public QRCode(QRCodeData data) : base(data) { } 17 | 18 | public Image GetGraphic(int pixelsPerModule) 19 | { 20 | return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, true); 21 | } 22 | 23 | public Image GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, bool drawQuietZones = true) 24 | { 25 | return this.GetGraphic(pixelsPerModule, Color.Parse(darkColorHtmlHex), Color.Parse(lightColorHtmlHex), drawQuietZones); 26 | } 27 | 28 | public Image GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true) 29 | { 30 | int moduleOffset = drawQuietZones ? 0 : 4; 31 | int size = (this.QrCodeData.ModuleMatrix.Count - moduleOffset * 2) * pixelsPerModule; 32 | 33 | var image = new Image(size, size); 34 | DrawQRCode(image, pixelsPerModule, moduleOffset, darkColor, lightColor); 35 | 36 | return image; 37 | } 38 | 39 | public Image GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Image icon = null, int iconSizePercent = 15, int iconBorderWidth = 0, bool drawQuietZones = true, Color? iconBackgroundColor = null) 40 | { 41 | Image img = GetGraphic(pixelsPerModule, darkColor, lightColor, drawQuietZones) as Image; 42 | if (icon != null && iconSizePercent > 0 && iconSizePercent <= 100) 43 | { 44 | float iconDestWidth = iconSizePercent * img.Width / 100f; 45 | float iconDestHeight = iconDestWidth * icon.Height / icon.Width; 46 | float iconX = (img.Width - iconDestWidth) / 2; 47 | float iconY = (img.Height - iconDestHeight) / 2; 48 | var centerDest = new RectangleF(iconX - iconBorderWidth, iconY - iconBorderWidth, iconDestWidth + iconBorderWidth * 2, iconDestHeight + iconBorderWidth * 2); 49 | var iconDestRect = new RectangleF(iconX, iconY, iconDestWidth, iconDestHeight); 50 | 51 | if (iconBorderWidth > 0) 52 | { 53 | if (!iconBackgroundColor.HasValue) 54 | iconBackgroundColor = lightColor; 55 | if (iconBackgroundColor != Color.Transparent) 56 | { 57 | img.ProcessPixelRows(accessor => 58 | { 59 | for (int y = (int)centerDest.Top; y <= (int)centerDest.Bottom; y++) 60 | { 61 | Span pixelRow = accessor.GetRowSpan(y); 62 | 63 | for (int x = (int)centerDest.Left; x <= (int)centerDest.Right; x++) 64 | { 65 | pixelRow[x] = iconBackgroundColor ?? lightColor; 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | 72 | var sizedIcon = icon.Clone(x => x.Resize((int)iconDestWidth, (int)iconDestHeight)); 73 | img.Mutate(x => x.DrawImage(sizedIcon, new Point((int)iconDestRect.X, (int)iconDestRect.Y), 1)); 74 | } 75 | /* 76 | 77 | 78 | var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; 79 | var offset = drawQuietZones ? 0 : 4 * pixelsPerModule; 80 | 81 | var image = new Image(size, size); 82 | 83 | using (var gfx = Graphics.FromImage(bmp)) 84 | using (var lightBrush = new SolidBrush(lightColor)) 85 | using (var darkBrush = new SolidBrush(darkColor)) 86 | { 87 | gfx.InterpolationMode = InterpolationMode.HighQualityBicubic; 88 | gfx.CompositingQuality = CompositingQuality.HighQuality; 89 | gfx.Clear(lightColor); 90 | var drawIconFlag = icon != null && iconSizePercent > 0 && iconSizePercent <= 100; 91 | 92 | for (var x = 0; x < size + offset; x = x + pixelsPerModule) 93 | { 94 | for (var y = 0; y < size + offset; y = y + pixelsPerModule) 95 | { 96 | var moduleBrush = this.QrCodeData.ModuleMatrix[(y + pixelsPerModule) / pixelsPerModule - 1][(x + pixelsPerModule) / pixelsPerModule - 1] ? darkBrush : lightBrush; 97 | gfx.FillRectangle(moduleBrush , new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule)); 98 | } 99 | } 100 | 101 | if (drawIconFlag) 102 | { 103 | float iconDestWidth = iconSizePercent * bmp.Width / 100f; 104 | float iconDestHeight = drawIconFlag ? iconDestWidth * icon.Height / icon.Width : 0; 105 | float iconX = (bmp.Width - iconDestWidth) / 2; 106 | float iconY = (bmp.Height - iconDestHeight) / 2; 107 | var centerDest = new RectangleF(iconX - iconBorderWidth, iconY - iconBorderWidth, iconDestWidth + iconBorderWidth * 2, iconDestHeight + iconBorderWidth * 2); 108 | var iconDestRect = new RectangleF(iconX, iconY, iconDestWidth, iconDestHeight); 109 | var iconBgBrush = iconBackgroundColor != null ? new SolidBrush((Color)iconBackgroundColor) : lightBrush; 110 | //Only render icon/logo background, if iconBorderWith is set > 0 111 | if (iconBorderWidth > 0) 112 | { 113 | using (GraphicsPath iconPath = CreateRoundedRectanglePath(centerDest, iconBorderWidth * 2)) 114 | { 115 | gfx.FillPath(iconBgBrush, iconPath); 116 | } 117 | } 118 | gfx.DrawImage(icon, iconDestRect, new RectangleF(0, 0, icon.Width, icon.Height), GraphicsUnit.Pixel); 119 | } 120 | 121 | gfx.Save(); 122 | } 123 | */ 124 | return img; 125 | } 126 | 127 | private void DrawQRCode(Image image, int pixelsPerModule, int moduleOffset, Color darkColor, Color lightColor) 128 | { 129 | Rgba32[] row = new Rgba32[image.Width]; 130 | 131 | image.ProcessPixelRows(accessor => 132 | { 133 | for (var modY = moduleOffset; modY < QrCodeData.ModuleMatrix.Count - moduleOffset; modY++) 134 | { 135 | // Generate row for this y-Module 136 | for (var modX = moduleOffset; modX < QrCodeData.ModuleMatrix.Count - moduleOffset; modX++) 137 | { 138 | for (var idx = 0; idx < pixelsPerModule; idx++) 139 | row[(modX - moduleOffset) * pixelsPerModule + idx] = this.QrCodeData.ModuleMatrix[modY][modX] ? darkColor : lightColor; 140 | } 141 | 142 | // Copy the prepared row to the image 143 | for (var idx = 0; idx < pixelsPerModule; idx++) 144 | { 145 | Span pixelRow = accessor.GetRowSpan((modY - moduleOffset) * pixelsPerModule + idx); 146 | row.CopyTo(pixelRow); 147 | } 148 | } 149 | }); 150 | } 151 | /* 152 | internal GraphicsPath CreateRoundedRectanglePath(RectangleF rect, int cornerRadius) 153 | { 154 | var roundedRect = new GraphicsPath(); 155 | roundedRect.AddArc(rect.X, rect.Y, cornerRadius * 2, cornerRadius * 2, 180, 90); 156 | roundedRect.AddLine(rect.X + cornerRadius, rect.Y, rect.Right - cornerRadius * 2, rect.Y); 157 | roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y, cornerRadius * 2, cornerRadius * 2, 270, 90); 158 | roundedRect.AddLine(rect.Right, rect.Y + cornerRadius * 2, rect.Right, rect.Y + rect.Height - cornerRadius * 2); 159 | roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y + rect.Height - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 0, 90); 160 | roundedRect.AddLine(rect.Right - cornerRadius * 2, rect.Bottom, rect.X + cornerRadius * 2, rect.Bottom); 161 | roundedRect.AddArc(rect.X, rect.Bottom - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 90, 90); 162 | roundedRect.AddLine(rect.X, rect.Bottom - cornerRadius * 2, rect.X, rect.Y + cornerRadius * 2); 163 | roundedRect.CloseFigure(); 164 | return roundedRect; 165 | } 166 | */ 167 | } 168 | 169 | public static class QRCodeHelper 170 | { 171 | public static Image GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Image icon = null, int iconSizePercent = 15, int iconBorderWidth = 0, bool drawQuietZones = true) 172 | { 173 | using (var qrGenerator = new QRCodeGenerator()) 174 | using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) 175 | using (var qrCode = new QRCode(qrCodeData)) 176 | return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, icon, iconSizePercent, iconBorderWidth, drawQuietZones); 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /QRCoderTests/assets/noun_Scientist_2909361.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /QRCoderTests/QRGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using QRCoder; 2 | using QRCoderTests.Helpers.XUnitExtenstions; 3 | using Shouldly; 4 | using System.Collections.Generic; 5 | using System.Reflection; 6 | using Xunit; 7 | using System.Linq; 8 | using System.Collections; 9 | using System.Text; 10 | 11 | namespace QRCoderTests 12 | { 13 | 14 | public class QRGeneratorTests 15 | { 16 | [Fact] 17 | [Category("QRGenerator/Antilog")] 18 | public void validate_antilogtable() 19 | { 20 | var gen = new QRCodeGenerator(); 21 | 22 | var checkString = string.Empty; 23 | var gField = gen.GetType().GetField("galoisField", BindingFlags.NonPublic | BindingFlags.Static); 24 | foreach (var listitem in (System.Collections.IEnumerable)gField.GetValue(gen)) 25 | { 26 | foreach (PropertyInfo prop in listitem.GetType().GetProperties()) 27 | checkString += prop.GetValue(listitem, null).ToString() + ","; 28 | 29 | checkString += ":"; 30 | } 31 | checkString.ShouldBe("0,1,:1,2,:2,4,:3,8,:4,16,:5,32,:6,64,:7,128,:8,29,:9,58,:10,116,:11,232,:12,205,:13,135,:14,19,:15,38,:16,76,:17,152,:18,45,:19,90,:20,180,:21,117,:22,234,:23,201,:24,143,:25,3,:26,6,:27,12,:28,24,:29,48,:30,96,:31,192,:32,157,:33,39,:34,78,:35,156,:36,37,:37,74,:38,148,:39,53,:40,106,:41,212,:42,181,:43,119,:44,238,:45,193,:46,159,:47,35,:48,70,:49,140,:50,5,:51,10,:52,20,:53,40,:54,80,:55,160,:56,93,:57,186,:58,105,:59,210,:60,185,:61,111,:62,222,:63,161,:64,95,:65,190,:66,97,:67,194,:68,153,:69,47,:70,94,:71,188,:72,101,:73,202,:74,137,:75,15,:76,30,:77,60,:78,120,:79,240,:80,253,:81,231,:82,211,:83,187,:84,107,:85,214,:86,177,:87,127,:88,254,:89,225,:90,223,:91,163,:92,91,:93,182,:94,113,:95,226,:96,217,:97,175,:98,67,:99,134,:100,17,:101,34,:102,68,:103,136,:104,13,:105,26,:106,52,:107,104,:108,208,:109,189,:110,103,:111,206,:112,129,:113,31,:114,62,:115,124,:116,248,:117,237,:118,199,:119,147,:120,59,:121,118,:122,236,:123,197,:124,151,:125,51,:126,102,:127,204,:128,133,:129,23,:130,46,:131,92,:132,184,:133,109,:134,218,:135,169,:136,79,:137,158,:138,33,:139,66,:140,132,:141,21,:142,42,:143,84,:144,168,:145,77,:146,154,:147,41,:148,82,:149,164,:150,85,:151,170,:152,73,:153,146,:154,57,:155,114,:156,228,:157,213,:158,183,:159,115,:160,230,:161,209,:162,191,:163,99,:164,198,:165,145,:166,63,:167,126,:168,252,:169,229,:170,215,:171,179,:172,123,:173,246,:174,241,:175,255,:176,227,:177,219,:178,171,:179,75,:180,150,:181,49,:182,98,:183,196,:184,149,:185,55,:186,110,:187,220,:188,165,:189,87,:190,174,:191,65,:192,130,:193,25,:194,50,:195,100,:196,200,:197,141,:198,7,:199,14,:200,28,:201,56,:202,112,:203,224,:204,221,:205,167,:206,83,:207,166,:208,81,:209,162,:210,89,:211,178,:212,121,:213,242,:214,249,:215,239,:216,195,:217,155,:218,43,:219,86,:220,172,:221,69,:222,138,:223,9,:224,18,:225,36,:226,72,:227,144,:228,61,:229,122,:230,244,:231,245,:232,247,:233,243,:234,251,:235,235,:236,203,:237,139,:238,11,:239,22,:240,44,:241,88,:242,176,:243,125,:244,250,:245,233,:246,207,:247,131,:248,27,:249,54,:250,108,:251,216,:252,173,:253,71,:254,142,:255,1,:"); 32 | } 33 | 34 | [Fact] 35 | [Category("QRGenerator/AlphanumDict")] 36 | public void validate_alphanumencdict() 37 | { 38 | var gen = new QRCodeGenerator(); 39 | 40 | var checkString = string.Empty; 41 | var gField = gen.GetType().GetField("alphanumEncDict", BindingFlags.NonPublic | BindingFlags.Static); 42 | foreach (var listitem in (Dictionary)gField.GetValue(gen)) 43 | { 44 | checkString += $"{listitem.Key},{listitem.Value}:"; 45 | } 46 | checkString.ShouldBe("0,0:1,1:2,2:3,3:4,4:5,5:6,6:7,7:8,8:9,9:A,10:B,11:C,12:D,13:E,14:F,15:G,16:H,17:I,18:J,19:K,20:L,21:M,22:N,23:O,24:P,25:Q,26:R,27:S,28:T,29:U,30:V,31:W,32:X,33:Y,34:Z,35: ,36:$,37:%,38:*,39:+,40:-,41:.,42:/,43::,44:"); 47 | } 48 | 49 | [Fact] 50 | [Category("QRGenerator/TextEncoding")] 51 | public void can_recognize_enconding_numeric() 52 | { 53 | var gen = new QRCodeGenerator(); 54 | MethodInfo method = gen.GetType().GetMethod("GetEncodingFromPlaintext", BindingFlags.NonPublic | BindingFlags.Static); 55 | var result = (int)method.Invoke(gen, new object[] { "0123456789", false }); 56 | 57 | result.ShouldBe(1); 58 | } 59 | 60 | 61 | [Fact] 62 | [Category("QRGenerator/TextEncoding")] 63 | public void can_recognize_enconding_alphanumeric() 64 | { 65 | var gen = new QRCodeGenerator(); 66 | MethodInfo method = gen.GetType().GetMethod("GetEncodingFromPlaintext", BindingFlags.NonPublic | BindingFlags.Static); 67 | var result = (int)method.Invoke(gen, new object[] { "0123456789ABC", false }); 68 | 69 | result.ShouldBe(2); 70 | } 71 | 72 | 73 | [Fact] 74 | [Category("QRGenerator/TextEncoding")] 75 | public void can_recognize_enconding_forced_bytemode() 76 | { 77 | var gen = new QRCodeGenerator(); 78 | MethodInfo method = gen.GetType().GetMethod("GetEncodingFromPlaintext", BindingFlags.NonPublic | BindingFlags.Static); 79 | var result = (int)method.Invoke(gen, new object[] { "0123456789", true }); 80 | 81 | result.ShouldBe(4); 82 | } 83 | 84 | 85 | [Fact] 86 | [Category("QRGenerator/TextEncoding")] 87 | public void can_recognize_enconding_byte() 88 | { 89 | var gen = new QRCodeGenerator(); 90 | MethodInfo method = gen.GetType().GetMethod("GetEncodingFromPlaintext", BindingFlags.NonPublic | BindingFlags.Static); 91 | var result = (int)method.Invoke(gen, new object[] { "0123456789äöüß", false }); 92 | 93 | result.ShouldBe(4); 94 | } 95 | 96 | [Fact] 97 | [Category("QRGenerator/TextEncoding")] 98 | public void can_encode_numeric() 99 | { 100 | var gen = new QRCodeGenerator(); 101 | var qrData = gen.CreateQrCode("123", QRCodeGenerator.ECCLevel.L); 102 | var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); 103 | result.ShouldBe("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111011111011111110000000010000010010100100000100000000101110100110001011101000000001011101001110010111010000000010111010001010101110100000000100000100001101000001000000001111111010101011111110000000000000000111110000000000000000110110100110101000001000000001110110000001010101100000000000110111100001101110000000000101111010011000001111000000000011101111100010011010000000000000000111110010101100000000111111100010111110001000000001000001000011101110010000000010111010101110110110100000000101110101011100011100000000001011101001100010001110000000010000010101001101010100000000111111101101000001110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); 104 | } 105 | 106 | [Fact] 107 | [Category("QRGenerator/TextEncoding")] 108 | public void can_encode_alphanumeric() 109 | { 110 | var gen = new QRCodeGenerator(); 111 | var qrData = gen.CreateQrCode("123ABC", QRCodeGenerator.ECCLevel.L); 112 | var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); 113 | result.ShouldBe("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111010111011111110000000010000010001100100000100000000101110101101001011101000000001011101011001010111010000000010111010100100101110100000000100000100111101000001000000001111111010101011111110000000000000000000110000000000000000111100101111110011101000000000111100010011110001110000000000100010100100000001000000000011110011111001110011000000001111101110101001000000000000000000000111100100100100000000111111100001100100110000000001000001000100001111110000000010111010010011111010100000000101110101111001011110000000001011101010101011000000000000010000010111001000010000000000111111101010010010010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); 114 | } 115 | 116 | [Fact] 117 | [Category("QRGenerator/TextEncoding")] 118 | public void can_encode_byte() 119 | { 120 | var gen = new QRCodeGenerator(); 121 | var qrData = gen.CreateQrCode("äöü", QRCodeGenerator.ECCLevel.L); 122 | var result = string.Join("", qrData.ModuleMatrix.Select(x => x.ToBitString()).ToArray()); 123 | result.ShouldBe("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111001011011111110000000010000010011100100000100000000101110101101101011101000000001011101001010010111010000000010111010001010101110100000000100000100000101000001000000001111111010101011111110000000000000000110110000000000000000111011111111011000100000000001001110001100010000010000000010011110001010001001000000000110011010000001000110000000001110001111001010110110000000000000000111101010011100000000111111101111011100110000000001000001010011101110010000000010111010110101110010100000000101110100110001000110000000001011101011001000100010000000010000010100000100011000000000111111101110101010111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); 124 | } 125 | } 126 | 127 | public static class ExtensionMethods 128 | { 129 | public static string ToBitString(this BitArray bits) 130 | { 131 | var sb = new StringBuilder(); 132 | int bitLength = 0; 133 | #if !NETCOREAPP1_1 134 | bitLength = bits.Count; 135 | #else 136 | bitLength = bits.Length; 137 | #endif 138 | for (int i = 0; i < bitLength; i++) 139 | { 140 | char c = bits[i] ? '1' : '0'; 141 | sb.Append(c); 142 | } 143 | 144 | return sb.ToString(); 145 | } 146 | } 147 | } 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /QRCoderTests/AsciiQRCodeRendererTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using QRCoder; 3 | using Shouldly; 4 | using QRCoderTests.Helpers.XUnitExtenstions; 5 | 6 | 7 | namespace QRCoderTests 8 | { 9 | 10 | public class AsciiQRCodeRendererTests 11 | { 12 | 13 | [Fact] 14 | [Category("QRRenderer/AsciiQRCode")] 15 | public void can_render_ascii_qrcode() 16 | { 17 | var targetCode = " \n \n \n \n ██████████████ ██ ██ ██████████████ \n ██ ██ ██ ████ ██ ██ \n ██ ██████ ██ ██ ██ ██ ██ ██████ ██ \n ██ ██████ ██ ██ ██ ██████ ██ \n ██ ██████ ██ ██ ██ ██████ ██ \n ██ ██ ████████ ██ ██ \n ██████████████ ██ ██ ██ ██████████████ \n ██ ████ \n ██████████ ████ ████████ ██ ████ \n ████ ██ ██ ████ ████████ ██ \n ██ ██ ██████████ ██ ██ ██ ████ \n ██ ██ ████ ████ ████ \n ████████ ██████ ████ ██ ██ \n ████████ \n ██████████████ ████ ████ ██ ████ ████ \n ██ ██ ████████ \n ██ ██████ ██ ██ ██ ██ ██ ██ ██ \n ██ ██████ ██ ██████ ██ ██ \n ██ ██████ ██ ██ ██ ██ ██ ████ ████ \n ██ ██ ████ ████ ██ ██ \n ██████████████ ██████ ██ ██████ \n \n \n \n "; 18 | 19 | //Create QR code 20 | var gen = new QRCodeGenerator(); 21 | var data = gen.CreateQrCode("A05", QRCodeGenerator.ECCLevel.Q); 22 | var asciiCode = new AsciiQRCode(data).GetGraphic(1); 23 | 24 | asciiCode.ShouldBe(targetCode); 25 | } 26 | 27 | [Fact] 28 | [Category("QRRenderer/AsciiQRCode")] 29 | public void can_render_ascii_qrcode_without_quietzones() 30 | { 31 | var targetCode = "██████████████ ██ ██ ██████████████\n██ ██ ██ ████ ██ ██\n██ ██████ ██ ██ ██ ██ ██ ██████ ██\n██ ██████ ██ ██ ██ ██████ ██\n██ ██████ ██ ██ ██ ██████ ██\n██ ██ ████████ ██ ██\n██████████████ ██ ██ ██ ██████████████\n ██ ████ \n██████████ ████ ████████ ██ ████ \n████ ██ ██ ████ ████████ ██\n ██ ██ ██████████ ██ ██ ██ ████ \n██ ██ ████ ████ ████ \n ████████ ██████ ████ ██ ██\n ████████ \n██████████████ ████ ████ ██ ████ ████\n██ ██ ████████ \n██ ██████ ██ ██ ██ ██ ██ ██ ██\n██ ██████ ██ ██████ ██ ██ \n██ ██████ ██ ██ ██ ██ ██ ████ ████\n██ ██ ████ ████ ██ ██ \n██████████████ ██████ ██ ██████"; 32 | 33 | //Create QR code 34 | var gen = new QRCodeGenerator(); 35 | var data = gen.CreateQrCode("A05", QRCodeGenerator.ECCLevel.Q); 36 | var asciiCode = new AsciiQRCode(data).GetGraphic(1, drawQuietZones : false); 37 | 38 | asciiCode.ShouldBe(targetCode); 39 | } 40 | 41 | [Fact] 42 | [Category("QRRenderer/AsciiQRCode")] 43 | public void can_render_ascii_qrcode_with_custom_symbols() 44 | { 45 | var targetCode = " \n \n \n \n \n \n \n \n XXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXX \n XXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXX \n XX XX XXXXXX XX XX XX \n XX XX XXXXXX XX XX XX \n XX XXXXXX XX XXXXXXXX XX XXXXXX XX \n XX XXXXXX XX XXXXXXXX XX XXXXXX XX \n XX XXXXXX XX XXXX XX XXXXXX XX \n XX XXXXXX XX XXXX XX XXXXXX XX \n XX XXXXXX XX XX XX XX XXXXXX XX \n XX XXXXXX XX XX XX XX XXXXXX XX \n XX XX XX XX XX \n XX XX XX XX XX \n XXXXXXXXXXXXXX XX XX XX XXXXXXXXXXXXXX \n XXXXXXXXXXXXXX XX XX XX XXXXXXXXXXXXXX \n XXXXXXXX \n XXXXXXXX \n XX XXXXXX XXXXXX XX XX XX \n XX XXXXXX XXXXXX XX XX XX \n XX XXXXXX XXXX XXXXXXXX XXXXXX XX \n XX XXXXXX XXXX XXXXXXXX XXXXXX XX \n XX XX XX XX XX XX \n XX XX XX XX XX XX \n XX XX XX XX XXXXXX \n XX XX XX XX XXXXXX \n XX XXXXXXXX XXXX XX XXXXXXXX XX \n XX XXXXXXXX XXXX XX XXXXXXXX XX \n XX XXXXXXXX XXXX \n XX XXXXXXXX XXXX \n XXXXXXXXXXXXXX XXXXXXXX XX XXXXXX \n XXXXXXXXXXXXXX XXXXXXXX XX XXXXXX \n XX XX XXXXXX XXXXXXXX \n XX XX XXXXXX XXXXXXXX \n XX XXXXXX XX XX XXXX XX XXXX \n XX XXXXXX XX XX XXXX XX XXXX \n XX XXXXXX XX XXXX XXXXXXXX \n XX XXXXXX XX XXXX XXXXXXXX \n XX XXXXXX XX XX XXXXXXXX XX XXXXXX \n XX XXXXXX XX XX XXXXXXXX XX XXXXXX \n XX XX XX XXXX XX \n XX XX XX XXXX XX \n XXXXXXXXXXXXXX XX XXXXXX XXXX XXXX \n XXXXXXXXXXXXXX XX XXXXXX XXXX XXXX \n \n \n \n \n \n \n \n "; 46 | 47 | //Create QR code 48 | var gen = new QRCodeGenerator(); 49 | var data = gen.CreateQrCode("A", QRCodeGenerator.ECCLevel.Q); 50 | var asciiCode = new AsciiQRCode(data).GetGraphic(2, "X", " "); 51 | 52 | asciiCode.ShouldBe(targetCode); 53 | } 54 | 55 | [Fact] 56 | [Category("QRRenderer/AsciiQRCode")] 57 | public void can_instantate_parameterless() 58 | { 59 | var asciiCode = new AsciiQRCode(); 60 | asciiCode.ShouldNotBeNull(); 61 | asciiCode.ShouldBeOfType(); 62 | } 63 | 64 | [Fact] 65 | [Category("QRRenderer/AsciiQRCode")] 66 | public void can_render_ascii_qrcode_from_helper() 67 | { 68 | var targetCode = " \n \n \n \n \n \n \n \n XXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXX \n XXXXXXXXXXXXXX XXXX XXXXXXXXXXXXXX \n XX XX XXXXXX XX XX XX \n XX XX XXXXXX XX XX XX \n XX XXXXXX XX XXXXXXXX XX XXXXXX XX \n XX XXXXXX XX XXXXXXXX XX XXXXXX XX \n XX XXXXXX XX XXXX XX XXXXXX XX \n XX XXXXXX XX XXXX XX XXXXXX XX \n XX XXXXXX XX XX XX XX XXXXXX XX \n XX XXXXXX XX XX XX XX XXXXXX XX \n XX XX XX XX XX \n XX XX XX XX XX \n XXXXXXXXXXXXXX XX XX XX XXXXXXXXXXXXXX \n XXXXXXXXXXXXXX XX XX XX XXXXXXXXXXXXXX \n XXXXXXXX \n XXXXXXXX \n XX XXXXXX XXXXXX XX XX XX \n XX XXXXXX XXXXXX XX XX XX \n XX XXXXXX XXXX XXXXXXXX XXXXXX XX \n XX XXXXXX XXXX XXXXXXXX XXXXXX XX \n XX XX XX XX XX XX \n XX XX XX XX XX XX \n XX XX XX XX XXXXXX \n XX XX XX XX XXXXXX \n XX XXXXXXXX XXXX XX XXXXXXXX XX \n XX XXXXXXXX XXXX XX XXXXXXXX XX \n XX XXXXXXXX XXXX \n XX XXXXXXXX XXXX \n XXXXXXXXXXXXXX XXXXXXXX XX XXXXXX \n XXXXXXXXXXXXXX XXXXXXXX XX XXXXXX \n XX XX XXXXXX XXXXXXXX \n XX XX XXXXXX XXXXXXXX \n XX XXXXXX XX XX XXXX XX XXXX \n XX XXXXXX XX XX XXXX XX XXXX \n XX XXXXXX XX XXXX XXXXXXXX \n XX XXXXXX XX XXXX XXXXXXXX \n XX XXXXXX XX XX XXXXXXXX XX XXXXXX \n XX XXXXXX XX XX XXXXXXXX XX XXXXXX \n XX XX XX XXXX XX \n XX XX XX XXXX XX \n XXXXXXXXXXXXXX XX XXXXXX XXXX XXXX \n XXXXXXXXXXXXXX XX XXXXXX XXXX XXXX \n \n \n \n \n \n \n \n "; 69 | 70 | //Create QR code 71 | var asciiCode = AsciiQRCodeHelper.GetQRCode("A", 2, "X", " ", QRCodeGenerator.ECCLevel.Q); 72 | asciiCode.ShouldBe(targetCode); 73 | } 74 | } 75 | } 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /QRCoder/PngByteQRCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using static QRCoder.QRCodeGenerator; 5 | 6 | namespace QRCoder 7 | { 8 | public sealed class PngByteQRCode : AbstractQRCode, IDisposable 9 | { 10 | /// 11 | /// Constructor without params to be used in COM Objects connections 12 | /// 13 | public PngByteQRCode() { } 14 | 15 | public PngByteQRCode(QRCodeData data) : base(data) 16 | { 17 | } 18 | 19 | 20 | /// 21 | /// Creates a black & white PNG of the QR code, using 1-bit grayscale. 22 | /// 23 | public byte[] GetGraphic(int pixelsPerModule, bool drawQuietZones = true) 24 | { 25 | using (var png = new PngBuilder()) 26 | { 27 | var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; 28 | png.WriteHeader(size, size, 1, PngBuilder.ColorType.Greyscale); 29 | png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones)); 30 | png.WriteEnd(); 31 | return png.GetBytes(); 32 | } 33 | } 34 | 35 | /// 36 | /// 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. 37 | /// 38 | public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, bool drawQuietZones = true) 39 | { 40 | using (var png = new PngBuilder()) 41 | { 42 | var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; 43 | png.WriteHeader(size, size, 1, PngBuilder.ColorType.Indexed); 44 | png.WritePalette(darkColorRgba, lightColorRgba); 45 | png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones)); 46 | png.WriteEnd(); 47 | return png.GetBytes(); 48 | } 49 | } 50 | 51 | /// 52 | /// Creates a bitmap where each pixel is represented by a single bit, dark = 0 and light = 1. 53 | /// 54 | private byte[] DrawScanlines(int pixelsPerModule, bool drawQuietZones) 55 | { 56 | var moduleMatrix = this.QrCodeData.ModuleMatrix; 57 | var matrixSize = moduleMatrix.Count - (drawQuietZones ? 0 : 8); 58 | var quietZoneOffset = (drawQuietZones ? 0 : 4); 59 | var bytesPerScanline = (matrixSize * pixelsPerModule + 7) / 8 + 1; // A monochrome scanline is one byte for filter type then one bit per pixel. 60 | var scanlines = new byte[bytesPerScanline * matrixSize * pixelsPerModule]; 61 | 62 | for (var y = 0; y < matrixSize; y++) 63 | { 64 | var modules = moduleMatrix[y+quietZoneOffset]; 65 | var scanlineOffset = y * pixelsPerModule * bytesPerScanline; 66 | 67 | // Draw a scanline with the modules from the QR code. 68 | for (var x = 0; x < matrixSize; x++) 69 | { 70 | if (modules[x + quietZoneOffset]) 71 | { 72 | continue; 73 | } 74 | 75 | var pixelIndex = x * pixelsPerModule; 76 | var endIndex = pixelIndex + pixelsPerModule; 77 | for (; pixelIndex < endIndex; pixelIndex++) 78 | { 79 | scanlines[scanlineOffset + 1 + pixelIndex / 8] |= (byte)(0x80 >> (pixelIndex % 8)); 80 | } 81 | } 82 | 83 | // Copy the scanline required number of times. 84 | for (var copyCount = 1; copyCount < pixelsPerModule; copyCount++) 85 | { 86 | Array.Copy(scanlines, scanlineOffset, scanlines, scanlineOffset + copyCount * bytesPerScanline, bytesPerScanline); 87 | } 88 | } 89 | 90 | return scanlines; 91 | } 92 | 93 | /// 94 | /// Writes the chunks that make up a PNG file. 95 | /// 96 | /// 97 | /// See https://www.w3.org/TR/2003/REC-PNG-20031110 and https://www.ietf.org/rfc/rfc1950.txt. 98 | /// 99 | private sealed class PngBuilder : IDisposable 100 | { 101 | private static readonly byte[] PngSignature = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; 102 | 103 | private static readonly uint[] CrcTable = { 104 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 105 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 106 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 107 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D 108 | }; 109 | 110 | // ReSharper disable InconsistentNaming 111 | // Chunk types 112 | private static readonly byte[] IHDR = { 73, 72, 68, 82 }; 113 | 114 | private static readonly byte[] IDAT = { 73, 68, 65, 84 }; 115 | 116 | private static readonly byte[] IEND = { 73, 69, 78, 68 }; 117 | 118 | private static readonly byte[] PLTE = { 80, 76, 84, 69 }; 119 | 120 | private static readonly byte[] tRNS = { 116, 82, 78, 83 }; 121 | // ReSharper enable InconsistentNaming 122 | 123 | public enum ColorType : byte 124 | { 125 | Greyscale = 0, 126 | Indexed = 3 127 | } 128 | 129 | private MemoryStream stream = new MemoryStream(); 130 | 131 | public void Dispose() 132 | { 133 | this.stream?.Dispose(); 134 | this.stream = null; 135 | } 136 | 137 | public byte[] GetBytes() 138 | { 139 | var bytes = this.stream.ToArray(); 140 | 141 | // Enumerate chunks in file and insert their CRC32 checksums. 142 | var chunkOffset = PngSignature.Length; 143 | while (chunkOffset < bytes.Length) 144 | { 145 | // Read length field. 146 | var dataLength = (bytes[chunkOffset] << 24) | (bytes[chunkOffset + 1] << 16) | (bytes[chunkOffset + 2] << 8) | bytes[chunkOffset + 3]; 147 | 148 | // CRC is computed from type and data fields. 149 | var crc = Crc32(bytes, chunkOffset + 4, dataLength + 4); 150 | 151 | // Write CRC to end of chunk. 152 | var crcOffset = chunkOffset + 8 + dataLength; 153 | bytes[crcOffset + 0] = (byte)(crc >> 24); 154 | bytes[crcOffset + 1] = (byte)(crc >> 16); 155 | bytes[crcOffset + 2] = (byte)(crc >> 8); 156 | bytes[crcOffset + 3] = (byte)crc; 157 | 158 | // Seek to next chunk. 159 | chunkOffset = crcOffset + 4; 160 | } 161 | 162 | return bytes; 163 | } 164 | 165 | /// 166 | /// Writes the IHDR chunk. This must be the first chunk in the file. 167 | /// 168 | public void WriteHeader(int width, int height, byte bitDepth, ColorType colorType) 169 | { 170 | this.stream.Write(PngSignature, 0, PngSignature.Length); 171 | this.WriteChunkStart(IHDR, 13); 172 | 173 | // Size. 174 | this.WriteIntBigEndian((uint)width); 175 | this.WriteIntBigEndian((uint)height); 176 | 177 | // Color. 178 | this.stream.WriteByte(bitDepth); 179 | this.stream.WriteByte((byte)colorType); 180 | 181 | // Constants. 182 | this.stream.WriteByte(0); 183 | this.stream.WriteByte(0); 184 | this.stream.WriteByte(0); 185 | 186 | this.WriteChunkEnd(); 187 | } 188 | 189 | /// 190 | /// Writes the PLTE chunk, and also the tRNS chunk if necessary. Must come before the IDAT chunk. 191 | /// 192 | public void WritePalette(params byte[][] rgbaColors) 193 | { 194 | const int Red = 0, Green = 1, Blue = 2, Alpha = 3; 195 | const byte Opaque = 255; 196 | var hasAlpha = false; 197 | 198 | this.WriteChunkStart(PLTE, 3 * rgbaColors.Length); 199 | foreach (var color in rgbaColors) 200 | { 201 | hasAlpha |= color.Length > Alpha && color[Alpha] < Opaque; 202 | this.stream.WriteByte(color[Red]); 203 | this.stream.WriteByte(color[Green]); 204 | this.stream.WriteByte(color[Blue]); 205 | } 206 | this.WriteChunkEnd(); 207 | 208 | if (!hasAlpha) 209 | { 210 | return; 211 | } 212 | 213 | this.WriteChunkStart(tRNS, rgbaColors.Length); 214 | foreach (var color in rgbaColors) 215 | { 216 | this.stream.WriteByte(color.Length > Alpha ? color[Alpha] : Opaque); 217 | } 218 | this.WriteChunkEnd(); 219 | } 220 | 221 | /// 222 | /// Writes the IDAT chunk with the actual picture. 223 | /// 224 | public void WriteScanlines(byte[] scanlines) 225 | { 226 | using (var idatStream = new MemoryStream()) 227 | { 228 | Deflate(idatStream, scanlines); 229 | 230 | this.WriteChunkStart(IDAT, (int)(idatStream.Length + 6)); 231 | 232 | // Deflate header. 233 | this.stream.WriteByte(0x78); // 8 Deflate algorithm, 7 max window size 234 | this.stream.WriteByte(0x9C); // Check bits. 235 | 236 | // Compressed data. 237 | idatStream.Position = 0; 238 | #if NET35 239 | idatStream.WriteTo(this.stream); 240 | #else 241 | idatStream.CopyTo(this.stream); 242 | #endif 243 | // Deflate checksum. 244 | var adler = Adler32(scanlines, 0, scanlines.Length); 245 | this.WriteIntBigEndian(adler); 246 | 247 | this.WriteChunkEnd(); 248 | } 249 | } 250 | 251 | /// 252 | /// Writes the IEND chunk. This must be the last chunk in the file. 253 | /// 254 | public void WriteEnd() 255 | { 256 | this.WriteChunkStart(IEND, 0); 257 | this.WriteChunkEnd(); 258 | } 259 | 260 | private void WriteChunkStart(byte[] type, int length) 261 | { 262 | this.WriteIntBigEndian((uint)length); 263 | this.stream.Write(type, 0, 4); 264 | } 265 | 266 | private void WriteChunkEnd() 267 | { 268 | // Reserves 4 bytes space for crc32 so GetBytes can add it later. 269 | this.stream.SetLength(this.stream.Length + 4); 270 | this.stream.Position += 4; 271 | } 272 | 273 | private void WriteIntBigEndian(uint value) 274 | { 275 | this.stream.WriteByte((byte)(value >> 24)); 276 | this.stream.WriteByte((byte)(value >> 16)); 277 | this.stream.WriteByte((byte)(value >> 8)); 278 | this.stream.WriteByte((byte)value); 279 | } 280 | 281 | private static void Deflate(Stream output, byte[] bytes) 282 | { 283 | using (var deflateStream = new DeflateStream(output, CompressionMode.Compress, leaveOpen: true)) 284 | { 285 | deflateStream.Write(bytes, 0, bytes.Length); 286 | } 287 | } 288 | 289 | // Reference implementation from RFC 1950. Not optimized. 290 | private static uint Adler32(byte[] data, int index, int length) 291 | { 292 | const uint Base = 65521; 293 | uint s1 = 1, s2 = 0; 294 | 295 | var end = index + length; 296 | for (var n = index; n < end; n++) 297 | { 298 | s1 = (s1 + data[n]) % Base; 299 | s2 = (s2 + s1) % Base; 300 | } 301 | 302 | return (s2 << 16) + s1; 303 | } 304 | 305 | // Reference implementation from REC-PNG-20031110. Not optimized. 306 | private static uint Crc32(byte[] data, int index, int length) 307 | { 308 | var c = 0xffffffff; 309 | 310 | var end = index + length; 311 | for (var n = index; n < end; n++) 312 | { 313 | c = CrcTable[(c ^ data[n]) & 0xff] ^ (c >> 8); 314 | } 315 | 316 | return c ^ 0xffffffff; 317 | } 318 | } 319 | } 320 | 321 | public static class PngByteQRCodeHelper 322 | { 323 | 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) 324 | { 325 | using (var qrGenerator = new QRCodeGenerator()) 326 | using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) 327 | using (var qrCode = new PngByteQRCode(qrCodeData)) 328 | return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba, drawQuietZones); 329 | } 330 | 331 | 332 | 333 | public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size, bool drawQuietZones = true) 334 | { 335 | using (var qrGen = new QRCodeGenerator()) 336 | using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) 337 | using (var qrPng = new PngByteQRCode(qrCode)) 338 | return qrPng.GetGraphic(size, drawQuietZones); 339 | } 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /QRCoder/SvgQRCode.cs: -------------------------------------------------------------------------------- 1 | using QRCoder.Extensions; 2 | using SixLabors.ImageSharp; 3 | using System; 4 | using System.Collections; 5 | using System.Text; 6 | using static QRCoder.QRCodeGenerator; 7 | using static QRCoder.SvgQRCode; 8 | 9 | namespace QRCoder 10 | { 11 | public class SvgQRCode : AbstractQRCode, IDisposable 12 | { 13 | /// 14 | /// Constructor without params to be used in COM Objects connections 15 | /// 16 | public SvgQRCode() { } 17 | public SvgQRCode(QRCodeData data) : base(data) { } 18 | 19 | /// 20 | /// Returns a QR code as SVG string 21 | /// 22 | /// The pixel size each b/w module is drawn 23 | /// SVG as string 24 | public string GetGraphic(int pixelsPerModule) 25 | { 26 | var viewBox = new Size(pixelsPerModule*QrCodeData.ModuleMatrix.Count, pixelsPerModule * QrCodeData.ModuleMatrix.Count); 27 | return this.GetGraphic(viewBox, Color.Black, Color.White); 28 | } 29 | 30 | /// 31 | /// Returns a QR code as SVG string with custom colors, optional quietzone and logo 32 | /// 33 | /// The pixel size each b/w module is drawn 34 | /// Color of the dark modules 35 | /// Color of the light modules 36 | /// If true a white border is drawn around the whole QR Code 37 | /// Defines if width/height or viewbox should be used for size definition 38 | /// A (optional) logo to be rendered on the code (either Bitmap or SVG) 39 | /// SVG as string 40 | public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) 41 | { 42 | var offset = drawQuietZones ? 0 : 4; 43 | var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule); 44 | var viewBox = new Size(edgeSize, edgeSize); 45 | return this.GetGraphic(viewBox, darkColor, lightColor, drawQuietZones, sizingMode, logo); 46 | } 47 | 48 | /// 49 | /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo 50 | /// 51 | /// The pixel size each b/w module is drawn 52 | /// The color of the dark/black modules in hex (e.g. #000000) representation 53 | /// The color of the light/white modules in hex (e.g. #ffffff) representation 54 | /// If true a white border is drawn around the whole QR Code 55 | /// Defines if width/height or viewbox should be used for size definition 56 | /// A (optional) logo to be rendered on the code (either Bitmap or SVG) 57 | /// SVG as string 58 | public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) 59 | { 60 | var offset = drawQuietZones ? 0 : 4; 61 | var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule); 62 | var viewBox = new Size(edgeSize, edgeSize); 63 | return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); 64 | } 65 | 66 | /// 67 | /// Returns a QR code as SVG string with optional quietzone and logo 68 | /// 69 | /// The viewbox of the QR code graphic 70 | /// If true a white border is drawn around the whole QR Code 71 | /// Defines if width/height or viewbox should be used for size definition 72 | /// A (optional) logo to be rendered on the code (either Bitmap or SVG) 73 | /// SVG as string 74 | public string GetGraphic(Size viewBox, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) 75 | { 76 | return this.GetGraphic(viewBox, Color.Black, Color.White, drawQuietZones, sizingMode, logo); 77 | } 78 | 79 | /// 80 | /// Returns a QR code as SVG string with custom colors and optional quietzone and logo 81 | /// 82 | /// The viewbox of the QR code graphic 83 | /// Color of the dark modules 84 | /// Color of the light modules 85 | /// If true a white border is drawn around the whole QR Code 86 | /// Defines if width/height or viewbox should be used for size definition 87 | /// A (optional) logo to be rendered on the code (either Bitmap or SVG) 88 | /// SVG as string 89 | public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) 90 | { 91 | return this.GetGraphic(viewBox, $"#{darkColor.ToHex()}", $"#{lightColor.ToHex()}", drawQuietZones, sizingMode, logo); 92 | } 93 | 94 | /// 95 | /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo 96 | /// 97 | /// The viewbox of the QR code graphic 98 | /// The color of the dark/black modules in hex (e.g. #000000) representation 99 | /// The color of the light/white modules in hex (e.g. #ffffff) representation 100 | /// If true a white border is drawn around the whole QR Code 101 | /// Defines if width/height or viewbox should be used for size definition 102 | /// A (optional) logo to be rendered on the code (either Bitmap or SVG) 103 | /// SVG as string 104 | public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) 105 | { 106 | int offset = drawQuietZones ? 0 : 4; 107 | int drawableModulesCount = this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : offset * 2); 108 | double pixelsPerModule = Math.Min(viewBox.Width, viewBox.Height) / (double)drawableModulesCount; 109 | double qrSize = drawableModulesCount * pixelsPerModule; 110 | string svgSizeAttributes = (sizingMode == SizingMode.WidthHeightAttribute) ? $@"width=""{viewBox.Width}"" height=""{viewBox.Height}""" : $@"viewBox=""0 0 {viewBox.Width} {viewBox.Height}"""; 111 | ImageAttributes? logoAttr = null; 112 | if (logo != null) 113 | logoAttr = GetLogoAttributes(logo, viewBox); 114 | 115 | // Merge horizontal rectangles 116 | int[,] matrix = new int[drawableModulesCount, drawableModulesCount]; 117 | for (int yi = 0; yi < drawableModulesCount; yi += 1) 118 | { 119 | BitArray bitArray = this.QrCodeData.ModuleMatrix[yi+offset]; 120 | 121 | int x0 = -1; 122 | int xL = 0; 123 | for (int xi = 0; xi < drawableModulesCount; xi += 1) 124 | { 125 | matrix[yi, xi] = 0; 126 | if (bitArray[xi+offset] && (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule))) 127 | { 128 | if(x0 == -1) 129 | { 130 | x0 = xi; 131 | } 132 | xL += 1; 133 | } 134 | else 135 | { 136 | if(xL > 0) 137 | { 138 | matrix[yi, x0] = xL; 139 | x0 = -1; 140 | xL = 0; 141 | } 142 | } 143 | } 144 | 145 | if (xL > 0) 146 | { 147 | matrix[yi, x0] = xL; 148 | } 149 | } 150 | 151 | StringBuilder svgFile = new StringBuilder($@""); 152 | svgFile.AppendLine($@""); 153 | for (int yi = 0; yi < drawableModulesCount; yi += 1) 154 | { 155 | double y = yi * pixelsPerModule; 156 | for (int xi = 0; xi < drawableModulesCount; xi += 1) 157 | { 158 | int xL = matrix[yi, xi]; 159 | if(xL > 0) 160 | { 161 | // Merge vertical rectangles 162 | int yL = 1; 163 | for (int y2 = yi + 1; y2 < drawableModulesCount; y2 += 1) 164 | { 165 | if(matrix[y2, xi] == xL) 166 | { 167 | matrix[y2, xi] = 0; 168 | yL += 1; 169 | } 170 | else 171 | { 172 | break; 173 | } 174 | } 175 | 176 | // Output SVG rectangles 177 | double x = xi * pixelsPerModule; 178 | if (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo(x, y, logoAttr, pixelsPerModule)) 179 | svgFile.AppendLine($@""); 180 | } 181 | } 182 | } 183 | 184 | //Render logo, if set 185 | if (logo != null) 186 | { 187 | if (!logo.IsEmbedded()) 188 | { 189 | svgFile.AppendLine($@""); 190 | svgFile.AppendLine($@""); 191 | svgFile.AppendLine(@""); 192 | } 193 | else 194 | { 195 | var rawLogo = (string)logo.GetRawLogo(); 196 | var svg = System.Xml.Linq.XDocument.Parse(rawLogo); 197 | svg.Root.SetAttributeValue("x", CleanSvgVal(logoAttr.Value.X)); 198 | svg.Root.SetAttributeValue("y", CleanSvgVal(logoAttr.Value.Y)); 199 | svg.Root.SetAttributeValue("width", CleanSvgVal(logoAttr.Value.Width)); 200 | svg.Root.SetAttributeValue("height", CleanSvgVal(logoAttr.Value.Height)); 201 | svg.Root.SetAttributeValue("shape-rendering", "geometricPrecision"); 202 | svgFile.AppendLine(svg.ToString(System.Xml.Linq.SaveOptions.DisableFormatting).Replace("svg:", "")); 203 | } 204 | } 205 | 206 | svgFile.Append(@""); 207 | return svgFile.ToString(); 208 | } 209 | 210 | private bool IsBlockedByLogo(double x, double y, ImageAttributes? attr, double pixelPerModule) 211 | { 212 | 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; 213 | } 214 | 215 | private ImageAttributes GetLogoAttributes(SvgLogo logo, Size viewBox) 216 | { 217 | var imgWidth = logo.GetIconSizePercent() / 100d * viewBox.Width; 218 | var imgHeight = logo.GetIconSizePercent() / 100d * viewBox.Height; 219 | var imgPosX = viewBox.Width / 2d - imgWidth / 2d; 220 | var imgPosY = viewBox.Height / 2d - imgHeight / 2d; 221 | return new ImageAttributes() 222 | { 223 | Width = imgWidth, 224 | Height = imgHeight, 225 | X = imgPosX, 226 | Y = imgPosY 227 | }; 228 | } 229 | 230 | private struct ImageAttributes 231 | { 232 | public double Width; 233 | public double Height; 234 | public double X; 235 | public double Y; 236 | } 237 | 238 | private string CleanSvgVal(double input) 239 | { 240 | //Clean double values for international use/formats 241 | //We use explicitly "G15" to avoid differences between .NET full and Core platforms 242 | //https://stackoverflow.com/questions/64898117/tostring-has-a-different-behavior-between-net-462-and-net-core-3-1 243 | return input.ToString("G15", System.Globalization.CultureInfo.InvariantCulture); 244 | } 245 | 246 | /// 247 | /// Mode of sizing attribution on svg root node 248 | /// 249 | public enum SizingMode 250 | { 251 | WidthHeightAttribute, 252 | ViewBoxAttribute 253 | } 254 | 255 | /// 256 | /// Represents a logo graphic that can be rendered on a SvgQRCode 257 | /// 258 | public class SvgLogo 259 | { 260 | private string _logoData; 261 | private MediaType _mediaType; 262 | private int _iconSizePercent; 263 | private bool _fillLogoBackground; 264 | private object _logoRaw; 265 | private bool _isEmbedded; 266 | 267 | 268 | /// 269 | /// Create a logo object to be used in SvgQRCode renderer 270 | /// 271 | /// Logo to be rendered as Bitmap/rasterized graphic 272 | /// Degree of percentage coverage of the QR code by the logo 273 | /// If true, the background behind the logo will be cleaned 274 | public SvgLogo(Image iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true) 275 | { 276 | _iconSizePercent = iconSizePercent; 277 | using (var ms = new System.IO.MemoryStream()) 278 | { 279 | iconRasterized.SaveAsPng(ms); 280 | _logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None); 281 | } 282 | _mediaType = MediaType.PNG; 283 | _fillLogoBackground = fillLogoBackground; 284 | _logoRaw = iconRasterized; 285 | _isEmbedded = false; 286 | } 287 | 288 | /// 289 | /// Create a logo object to be used in SvgQRCode renderer 290 | /// 291 | /// Logo to be rendered as SVG/vectorized graphic/string 292 | /// Degree of percentage coverage of the QR code by the logo 293 | /// If true, the background behind the logo will be cleaned 294 | /// If true, the logo will embedded as native svg instead of embedding it as image-tag 295 | public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true, bool iconEmbedded = true) 296 | { 297 | _iconSizePercent = iconSizePercent; 298 | _logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None); 299 | _mediaType = MediaType.SVG; 300 | _fillLogoBackground = fillLogoBackground; 301 | _logoRaw = iconVectorized; 302 | _isEmbedded = iconEmbedded; 303 | } 304 | 305 | /// 306 | /// Returns the raw logo's data 307 | /// 308 | /// 309 | public object GetRawLogo() 310 | { 311 | return _logoRaw; 312 | } 313 | 314 | /// 315 | /// Defines, if the logo shall be natively embedded. 316 | /// true=native svg embedding, false=embedding via image-tag 317 | /// 318 | /// 319 | public bool IsEmbedded() 320 | { 321 | return _isEmbedded; 322 | } 323 | 324 | /// 325 | /// Returns the media type of the logo 326 | /// 327 | /// 328 | public MediaType GetMediaType() 329 | { 330 | return _mediaType; 331 | } 332 | 333 | /// 334 | /// Returns the logo as data-uri 335 | /// 336 | /// 337 | public string GetDataUri() 338 | { 339 | return $"data:{_mediaType.GetStringValue()};base64,{_logoData}"; 340 | } 341 | 342 | /// 343 | /// Returns how much of the QR code should be covered by the logo (in percent) 344 | /// 345 | /// 346 | public int GetIconSizePercent() 347 | { 348 | return _iconSizePercent; 349 | } 350 | 351 | /// 352 | /// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo) 353 | /// 354 | /// 355 | public bool FillLogoBackground() 356 | { 357 | return _fillLogoBackground; 358 | } 359 | 360 | /// 361 | /// Media types for SvgLogos 362 | /// 363 | public enum MediaType : int 364 | { 365 | [StringValue("image/png")] 366 | PNG = 0, 367 | [StringValue("image/svg+xml")] 368 | SVG = 1 369 | } 370 | } 371 | } 372 | 373 | #if NET6_0_WINDOWS 374 | [System.Runtime.Versioning.SupportedOSPlatform("windows")] 375 | #endif 376 | public static class SvgQRCodeHelper 377 | { 378 | 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 logo = null) 379 | { 380 | using (var qrGenerator = new QRCodeGenerator()) 381 | using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) 382 | using (var qrCode = new SvgQRCode(qrCodeData)) 383 | return qrCode.GetGraphic(pixelsPerModule, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); 384 | } 385 | } 386 | } 387 | --------------------------------------------------------------------------------