This article was contributed by a reader. More friends are welcome to share.
Author: VleaStwo
1. Language Background
- .NET 6/7/8
- ASP.NET Core Blazor
2. Requirement Background
- Generate QR codes from URLs or other information
- For users to scan and view information on terminals
- Place certain text information around the QR code
- For users to view information directly (partial)
3. Solution Approach
- QR Code Generation Tool
There are many versions available for generating QR codes; here we choose Net.Codecrete.QrCodeGenerator v2.0.3
- Links:
GitHub: https://github.com/manuelbl/QrCodeGenerator
NuGet: https://www.nuget.org/packages/Net.Codecrete.QrCodeGenerator/2.0.3
No issues were encountered, so we didn't try other solutions; they are generally similar.
- Drawing Tool
- Based on a suggestion from an expert in the group, we attempted to use "Graphics" but failed

- According to Microsoft documentation, starting from .NET 6,
System.Drawingis only supported on Windows. Fortunately, the official documentation provides several alternatives:

We chose SkiaSharp v2.88.3
Links:
GitHub: https://github.com/mono/SkiaSharp
NuGet: https://www.nuget.org/packages/SkiaSharp/2.88.3
- Layout
The positional relationship between the QR code and the text is basically fixed. Whether you draw on paper or on a computer, it doesn't matter. The purpose of drawing in advance is to calculate positioning points conveniently. Ultimately, the layout design depends on your specific business needs (those with strong mental capacity can skip drawing and rely on brain cache).
Implementation Code
- QR Generation
No explanation needed; it's done with one line of code:
// Invocation
var qr = QrCode.EncodeText(mes, QrCode.Ecc.Quartile);
// Library source code
// Simplified comments
// text: Text to encode
// ecl: QR code quality level
public static QrCode EncodeText(string text, Ecc ecl)
{
Objects.RequireNonNull(text);
Objects.RequireNonNull(ecl);
var segments = QrSegment.MakeSegments(text);
return EncodeSegments(segments, ecl);
}
- Drawing
- The QR code needs to be converted into a bitmap-like class before further drawing
https://www.nuget.org/packages/Net.Codecrete.QrCodeGenerator/2.0.3
The documentation already explains that different platforms require different approaches:
Raster Images / Bitmaps
Starting with .NET 6, System.Drawing is only supported on Windows operating system and thus cannot be used for multi-platform libraries like this one. Therefore, ToBitmap() has been removed and three options are now offered in the form of method extensions.
To use it:
Select one of the imaging libraries below
Add the NuGet dependencies to your project
Copy the appropriate QrCodeBitmapExtensions.cs file to your project
Imaging library Recommendation NuGet dependencies Extension file
System.Drawing For Windows only projects System.Drawing.Common QrCodeBitmapExtensions.cs
SkiaSharp For macOS, Linux, iOS, Android and multi-platform projects SkiaSharp and SkiaSharp.NativeAssets.Linux (for Linux only) QrCodeBitmapExtensions.cs
ImageSharp Currently in beta state SixLabors.ImageSharp.Drawing QrCodeBitmapExtensions.cs
The general idea: choose the corresponding conversion method based on your needs. The GitHub repository already has corresponding demo extension classes that you can copy directly.
- We use SkiaSharp, so we need to convert the QR code to a usable SKBitmap. The extension class can be downloaded directly from the project's GitHub repository. Here is the direct link to the source code for download:
QrCode to SkiaSharp (SKBitmap) source code.
<param name="scale">The width and height, in pixels, of each module.</param>
<param name="border">The number of border modules to add to each of the four sides.</param>
var bmp = qr.ToBitmap(10, 2);
- After obtaining the bmp, based on my layout design, I use the image size as the base, with 1/10 of it as the character height
int h = bmp.Height / 10;
- Create a paint brush. Parameters are common, so no explanation needed
var paint = new SKPaint
{
Color = SKColors.Black,
IsAntialias = true,
IsLinearText = true,
TextEncoding = SKTextEncoding.Utf8,
Typeface = SKTypeface.FromFamilyName("Microsoft YaHei"),
TextSize = h,
TextAlign = SKTextAlign.Left,
};
- Create another brush with double the font size for the top area
var tempPaint = paint.Clone();
tempPaint.TextSize = 2 * h;
- Create the drawing surface (canvas)
SKSurface sKSurface = SKSurface.Create(
new SKImageInfo(bmp.Width * 3, bmp.Height * 2));
- Create the canvas and fill it with white
var canvas = sKSurface.Canvas;
canvas.DrawColor(SKColors.White);
- Draw the top area (normally, this should be a pre-drawn top pattern)
// Indent one unit from the left, then offset down by four units
canvas.DrawText(top,
new SKPoint(01 * h, 04 * h),
tempPaint);
// top is a string containing the text content for the top area
// SKPoint is a regular point composed of x and y
// Remember that the top-left corner is (0,0) and the bottom-right is (∞,∞)
- Draw the title area
// First, convert the string into List<string> according to your algorithm
// This is to wrap text when the line is too long
var titles = GetTextLines(title, 28).ToList();
for (int i = 0; i < titles.Count; i++)
{
canvas.DrawText(
titles[i],
new SKPoint(01 * h, (07 + i) * h),
paint);
}
- The word wrap algorithm source code is as follows
/// <summary>
/// Text wrapping algorithm
/// </summary>
/// <param name="text">Original text</param>
/// <param name="maxLength">Maximum characters per line</param>
/// <returns>Multiple lines</returns>
private static IEnumerable<string> GetTextLines(
string text, int maxLength)
{
// Note: maxLength is the number of characters, not the actual width
string[] words = text.Select(x => x.ToString()).ToArray(); //text.Split(' ');
List<string> lines = new List<string>();
string line = string.Empty;
foreach (string word in words)
{
//if (!string.IsNullOrEmpty(line))
//{
// line += " ";
//}
if (line.Length + word.Length <= maxLength)
{
line += word;
}
else
{
lines.Add(line);
line = word;
}
}
lines.Add(line);
return lines;
}
Note: This was found online with some modifications, not written from scratch
- QR code at the bottom left
// The QR code itself already has padding, so no extra indentation is needed
canvas.DrawBitmap(bmp, new SKPoint(00 * h, 10 * h));
- Text notes at the bottom right
int row = 1;
for (int i = 0; i < notes.Count; i++)
{
var strs = GetTextLines(notes[i], 18);
foreach (var str in strs)
{
// Position at 10h from the bottom right, with 1h indent on left and right
// row*h moves the text down
canvas.DrawText(str,
new SKPoint(10 * h, (10 + 1) * h + row * h),
paint);
row++;
}
}
- Finally, output the image
// filename: file name to save, ensure it is a PNG file
using (var image = sKSurface.Snapshot())
{
using (var writeStream = File.OpenWrite(filename))
{
// 80 is sufficient quality
image.Encode(
SKEncodedImageFormat.Png,
80)
.SaveTo(writeStream);
}
}
Actual Result
- Test code
static void Main(string[] args)
{
// QR code content
string mes = "https://www.dotnet9.com/";
// Title
string title = "ThingsGateway based on net6/7+, cross-platform edge acquisition (IoT) gateway";
// Text notes
List<string> notes = new List<string>()
{
"Blazor Server architecture, simpler development and deployment",
"Acquisition/upload configuration fully supports Excel import/export",
"Plugin-based drivers, convenient for secondary development",
"Time-series database storage",
"Real-time/historical alarm (SQL dump), supports boolean/high-low thresholds",
};
string filename =
Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".png");
// Top area content
string top = "Open Source .NET 7 and Blazor Combination Development";
// Encapsulated method
QrCreate.HorizontalDraw(mes, title, notes, filename, top);
Console.WriteLine(filename);
Console.ReadLine();
}
- Test result

Note: The image has a white background. If the current page is also white, the edges of the image may be hard to see. It is recommended to download the image and view it.