-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
ArtQRCode.cs
299 lines (266 loc) · 16 KB
/
ArtQRCode.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#if SYSTEM_DRAWING
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using static QRCoder.ArtQRCode;
using static QRCoder.QRCodeGenerator;
// pull request raised to extend library used.
namespace QRCoder
{
#if NET6_0_OR_GREATER
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
public class ArtQRCode : AbstractQRCode, IDisposable
{
/// <summary>
/// Constructor without params to be used in COM Objects connections
/// </summary>
public ArtQRCode() { }
/// <summary>
/// Creates new ArtQrCode object
/// </summary>
/// <param name="data">QRCodeData generated by the QRCodeGenerator</param>
public ArtQRCode(QRCodeData data) : base(data) { }
/// <summary>
/// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true)
/// </summary>
/// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
/// <returns>QRCode graphic as bitmap</returns>
public Bitmap GetGraphic(int pixelsPerModule)
{
return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent);
}
/// <summary>
/// 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)
/// </summary>
/// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
/// <returns>QRCode graphic as bitmap</returns>
public Bitmap GetGraphic(Bitmap backgroundImage = null)
{
return this.GetGraphic(10, Color.Black, Color.White, Color.Transparent, backgroundImage: backgroundImage);
}
/// <summary>
/// Renders an art-style QR code with dots as modules and various user settings
/// </summary>
/// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
/// <param name="darkColor">Color of the dark modules</param>
/// <param name="lightColor">Color of the light modules</param>
/// <param name="backgroundColor">Color of the background</param>
/// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
/// <param name="pixelSizeFactor">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.</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
/// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
/// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
/// <returns>QRCode graphic as bitmap</returns>
public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Bitmap backgroundImage = null, double pixelSizeFactor = 1,
bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted,
BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null)
{
if (pixelSizeFactor > 1)
throw new Exception("The parameter pixelSize must be between 0 and 1. (0-100%)");
int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule * pixelSizeFactor));
var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8);
var offset = (drawQuietZones ? 0 : 4);
var size = numModules * pixelsPerModule;
var bitmap = new Bitmap(size, size);
using (var graphics = Graphics.FromImage(bitmap))
{
using (var lightBrush = new SolidBrush(lightColor))
{
using (var darkBrush = new SolidBrush(darkColor))
{
// make background transparent
using (var brush = new SolidBrush(backgroundColor))
graphics.FillRectangle(brush, new Rectangle(0, 0, size, size));
//Render background if set
if (backgroundImage != null)
{
if (backgroundImageStyle == BackgroundImageStyle.Fill)
graphics.DrawImage(Resize(backgroundImage, size), 0, 0);
else if (backgroundImageStyle == BackgroundImageStyle.DataAreaOnly)
{
var bgOffset = 4 - offset;
graphics.DrawImage(Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)), 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule));
}
}
var darkModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, darkBrush);
var lightModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, lightBrush);
for (var x = 0; x < numModules; x += 1)
{
for (var y = 0; y < numModules; y += 1)
{
var rectangleF = new Rectangle(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule);
var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x];
var solidBrush = pixelIsDark ? darkBrush : lightBrush;
var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel;
if (!IsPartOfFinderPattern(x, y, numModules, offset))
if (drawQuietZones && quietZoneRenderingStyle == QuietZoneStyle.Flat && IsPartOfQuietZone(x, y, numModules))
graphics.FillRectangle(solidBrush, rectangleF);
else
graphics.DrawImage(pixelImage, rectangleF);
else if (finderPatternImage == null)
graphics.FillRectangle(solidBrush, rectangleF);
}
}
if (finderPatternImage != null)
{
var finderPatternSize = 7 * pixelsPerModule;
var finderPatternOffset = drawQuietZones ? 4 * pixelsPerModule : 0;
graphics.DrawImage(finderPatternImage, new Rectangle(finderPatternOffset, finderPatternOffset, finderPatternSize, finderPatternSize));
graphics.DrawImage(finderPatternImage, new Rectangle(size - finderPatternOffset - finderPatternSize, finderPatternOffset, finderPatternSize, finderPatternSize));
graphics.DrawImage(finderPatternImage, new Rectangle(finderPatternOffset, size - finderPatternOffset - finderPatternSize, finderPatternSize, finderPatternSize));
}
graphics.Save();
}
}
}
return bitmap;
}
/// <summary>
/// If the pixelSize is bigger than the pixelsPerModule or may end up filling the Module making a traditional QR code.
/// </summary>
/// <param name="pixelsPerModule">Pixels used per module rendered</param>
/// <param name="pixelSize">Size of the dots</param>
/// <param name="brush">Color of the pixels</param>
/// <returns></returns>
private Bitmap MakeDotPixel(int pixelsPerModule, int pixelSize, SolidBrush brush)
{
// draw a dot
var bitmap = new Bitmap(pixelSize, pixelSize);
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.FillEllipse(brush, new Rectangle(0, 0, pixelSize, pixelSize));
graphics.Save();
}
var pixelWidth = Math.Min(pixelsPerModule, pixelSize);
var margin = Math.Max((pixelsPerModule - pixelWidth) / 2, 0);
// center the dot in the module and crop to stay the right size.
var cropped = new Bitmap(pixelsPerModule, pixelsPerModule);
using (var graphics = Graphics.FromImage(cropped))
{
graphics.DrawImage(bitmap, new Rectangle(margin, margin, pixelWidth, pixelWidth),
new RectangleF(((float)pixelSize - pixelWidth) / 2, ((float)pixelSize - pixelWidth) / 2, pixelWidth, pixelWidth),
GraphicsUnit.Pixel);
graphics.Save();
}
return cropped;
}
/// <summary>
/// Checks if a given module(-position) is part of the quietzone of a QR code
/// </summary>
/// <param name="x">X position</param>
/// <param name="y">Y position</param>
/// <param name="numModules">Total number of modules per row</param>
/// <returns>true, if position is part of quiet zone</returns>
private bool IsPartOfQuietZone(int x, int y, int numModules)
{
return
x < 4 || //left
y < 4 || //top
x > numModules - 5 || //right
y > numModules - 5; //bottom
}
/// <summary>
/// Checks if a given module(-position) is part of one of the three finder patterns of a QR code
/// </summary>
/// <param name="x">X position</param>
/// <param name="y">Y position</param>
/// <param name="numModules">Total number of modules per row</param>
/// <param name="offset">Offset in modules (usually depending on drawQuietZones parameter)</param>
/// <returns>true, if position is part of any finder pattern</returns>
private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset)
{
var cornerSize = 11 - offset;
var outerLimitLow = (numModules - cornerSize - 1);
var outerLimitHigh = outerLimitLow + 8;
var invertedOffset = 4 - offset;
return
(x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern
(x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern
(x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern
}
/// <summary>
/// Resize to a square bitmap, but maintain the aspect ratio by padding transparently.
/// </summary>
/// <param name="image"></param>
/// <param name="newSize"></param>
/// <returns>Resized image as bitmap</returns>
private Bitmap Resize(Bitmap image, int newSize)
{
if (image == null) return null;
float scale = Math.Min((float)newSize / image.Width, (float)newSize / image.Height);
var scaledWidth = (int)(image.Width * scale);
var scaledHeight = (int)(image.Height * scale);
var offsetX = (newSize - scaledWidth) / 2;
var offsetY = (newSize - scaledHeight) / 2;
var scaledImage = new Bitmap(image, new Size(scaledWidth, scaledHeight));
var bm = new Bitmap(newSize, newSize);
using (Graphics graphics = Graphics.FromImage(bm))
{
using (var brush = new SolidBrush(Color.Transparent))
{
graphics.FillRectangle(brush, new Rectangle(0, 0, newSize, newSize));
graphics.InterpolationMode = InterpolationMode.High;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.DrawImage(scaledImage, new Rectangle(offsetX, offsetY, scaledWidth, scaledHeight));
}
}
return bm;
}
/// <summary>
/// Defines how the quiet zones shall be rendered.
/// </summary>
public enum QuietZoneStyle
{
Dotted,
Flat
}
/// <summary>
/// Defines how the background image (if set) shall be rendered.
/// </summary>
public enum BackgroundImageStyle
{
Fill,
DataAreaOnly
}
}
#if NET6_0_OR_GREATER
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
public static class ArtQRCodeHelper
{
/// <summary>
/// Helper function to create an ArtQRCode graphic with a single function call
/// </summary>
/// <param name="plainText">Text/payload to be encoded inside the QR code</param>
/// <param name="pixelsPerModule">Amount of px each dark/light module of the QR code shall take place in the final QR code image</param>
/// <param name="darkColor">Color of the dark modules</param>
/// <param name="lightColor">Color of the light modules</param>
/// <param name="backgroundColor">Color of the background</param>
/// <param name="eccLevel">The level of error correction data</param>
/// <param name="forceUtf8">Shall the generator be forced to work in UTF-8 mode?</param>
/// <param name="utf8BOM">Should the byte-order-mark be used?</param>
/// <param name="eciMode">Which ECI mode shall be used?</param>
/// <param name="requestedVersion">Set fixed QR code target version.</param>
/// <param name="backgroundImage">A bitmap object that will be used as background picture</param>
/// <param name="pixelSizeFactor">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.</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="quietZoneRenderingStyle">Style of the quiet zones</param>
/// <param name="backgroundImageStyle">Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone</param>
/// <param name="finderPatternImage">Optional image that should be used instead of the default finder patterns</param>
/// <returns>QRCode graphic as bitmap</returns>
public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false,
bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap backgroundImage = null, double pixelSizeFactor = 1.0,
bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat,
BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null)
{
using (var qrGenerator = new QRCodeGenerator())
using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
using (var qrCode = new ArtQRCode(qrCodeData))
return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, backgroundImage, pixelSizeFactor, drawQuietZones, quietZoneRenderingStyle, backgroundImageStyle, finderPatternImage);
}
}
}
#endif