├── 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($@"");
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 |
--------------------------------------------------------------------------------