How can I generate circular QR codes?

926 Views Asked by At

How can I generate circular QR codes? I would like to generate a QR code that is drawn with circles instead of squares. I currently have this code:

public Bitmap GenerateQR(string text)
{
    BarcodeWriter br = new BarcodeWriter();
    EncodingOptions encodingOptions = new EncodingOptions()
    {
        Width = 300,
        Height = 300,
        Margin = 1,
        PureBarcode = false,
    };

    encodingOptions.Hints.Add(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    br.Options = encodingOptions;
    br.Format = BarcodeFormat.QR_CODE;

    if (!string.IsNullOrWhiteSpace(text))
    {
        br.Renderer = new BitmapRenderer()
        {
            Foreground = Color.Black,
            Background = Color.White,
        };

        return br.Write(texto);
    }
}

I use C# and the Zxing library.

I need something like this:

enter image description here

4

There are 4 best solutions below

0
Selaka Nanayakkara On BEST ANSWER

Well, it's not a straightforward approach. But based on the @Curtis Yallop answer here.

Here is the C# equivalent implementation.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using ZXing;
using ZXing.Common;
using ZXing.QrCode.Internal;
using Encoder = ZXing.QrCode.Internal.Encoder;

class Program
{
    static void Main()
    {
        try
        {
            GenerateQRCodeImage("https://www.google.com", 300, 300, "./MyQRCode.png");
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

    private static void GenerateQRCodeImage(string text, int width, int height, string filePath)
    {
        Dictionary<EncodeHintType, object> encodingHints = new Dictionary<EncodeHintType, object>
        {
            { EncodeHintType.CHARACTER_SET, "UTF-8" }
        };
        QRCode code = System.Drawing.Imaging.Encoder.encode(text, ErrorCorrectionLevel.H, encodingHints);
        Bitmap image = RenderQRImage(code, width, height, 4);

        using (FileStream stream = new FileStream(filePath, FileMode.Create))
        {
            image.Save(stream, ImageFormat.Png);
        }
    }

    private static Bitmap RenderQRImage(QRCode code, int width, int height, int quietZone)
    {
        Bitmap image = new Bitmap(width, height);
        using (Graphics graphics = Graphics.FromImage(image))
        {
            graphics.SmoothingMode = SmoothingMode.AntiAlias;
            graphics.Clear(Color.White);
            graphics.DrawImage(image, 0, 0, width, height);

            ByteMatrix input = code.Matrix;
            if (input == null)
            {
                throw new InvalidOperationException();
            }
            int inputWidth = input.Width;
            int inputHeight = input.Height;
            int qrWidth = inputWidth + (quietZone * 2);
            int qrHeight = inputHeight + (quietZone * 2);
            int outputWidth = Math.Max(width, qrWidth);
            int outputHeight = Math.Max(height, qrHeight);

            int multiple = Math.Min(outputWidth / qrWidth, outputHeight / qrHeight);
            int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
            int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
            const int FINDER_PATTERN_SIZE = 7;
            const float CIRCLE_SCALE_DOWN_FACTOR = 21f / 30f;
            int circleSize = (int)(multiple * CIRCLE_SCALE_DOWN_FACTOR);

            for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple)
            {
                for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple)
                {
                    if (input[inputX, inputY] == 1)
                    {
                        if (!(inputX <= FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE ||
                              inputX >= inputWidth - FINDER_PATTERN_SIZE && inputY <= FINDER_PATTERN_SIZE ||
                              inputX <= FINDER_PATTERN_SIZE && inputY >= inputHeight - FINDER_PATTERN_SIZE))
                        {
                            graphics.FillEllipse(Brushes.Black, outputX, outputY, circleSize, circleSize);
                        }
                    }
                }
            }

            int circleDiameter = multiple * FINDER_PATTERN_SIZE;
            DrawFinderPatternCircleStyle(graphics, leftPadding, topPadding, circleDiameter);
            DrawFinderPatternCircleStyle(graphics, leftPadding + (inputWidth - FINDER_PATTERN_SIZE) * multiple, topPadding, circleDiameter);
            DrawFinderPatternCircleStyle(graphics, leftPadding, topPadding + (inputHeight - FINDER_PATTERN_SIZE) * multiple, circleDiameter);
        }

        return image;
    }

    private static void DrawFinderPatternCircleStyle(Graphics graphics, int x, int y, int circleDiameter)
    {
        var WHITE_CIRCLE_DIAMETER = 5 * circleDiameter / 7;
        var WHITE_CIRCLE_OFFSET = circleDiameter / 7;
        var MIDDLE_DOT_DIAMETER = 3 * circleDiameter / 7;
        var MIDDLE_DOT_OFFSET = 2 * circleDiameter / 7;

        graphics.FillEllipse(Brushes.Black, x, y, circleDiameter, circleDiameter);
        graphics.FillEllipse(Brushes.White, x + WHITE_CIRCLE_OFFSET, y + WHITE_CIRCLE_OFFSET, WHITE_CIRCLE_DIAMETER, WHITE_CIRCLE_DIAMETER);
        graphics.FillEllipse(Brushes.Black, x + MIDDLE_DOT_OFFSET, y + MIDDLE_DOT_OFFSET, MIDDLE_DOT_DIAMETER, MIDDLE_DOT_DIAMETER);
    }
}
1
Keshab Kumar On

I used C# and the Zxing library

We start by making a simple console application. We will be using .NET 7, but we can also use .NET 8 or 6.

After we have created the console application, we can install the package ZXing.NET. Note that they have separated the different targets into different packages. If we want to use ZXing for Android, we need a different NuGet package than when we target a Windows machine.

I will be using a Windows machine, so I install the following packages: Installing packages Install-Package ZXing.Net Install-Package ZXing.Net.Bindings.Windows.Compatibility That’s it! Now we will create the code to generate the QR code. Creating the QR Code

string imageFileName = "klc_qr_code.png";

QrCodeEncodingOptions options = new() {
DisableECI = true,
CharacterSet = "UTF-8",
Width = 500,
Height = 500
};

BarcodeWriter writer = new() {
Format = BarcodeFormat.QR_CODE,
Options = options
};
Bitmap qrCodeBitmap = writer.Write("This is my QR code!");
qrCodeBitmap.Save(imageFileName);

First, we define the image file. we didn’t add a path, so this file will be saved in the same directory as the debug application (path to our project/bin/Debug/net7.0).

Next up is the QrCodeEndodingOptions, which is the settings for the QR code. I think that most of the options are clear, except for the DisableECI. ECI is a specification for QR codes, but some readers have trouble reading QR codes with this specification. So it’s best to disable it… Why not disable it by default? Just saying.

Anyway, next is the BarcodeWriter. Now, the first time we tried this code I was “Barcode… Writer?” ZXing is not only compatible with creating QR codes but also barcodes and other types. Pretty cool, huh?

The property Format is where you set what kind of graphic you want to create. we have set it to QR_CODE, but feel free to explore other options.

Then we write text to the writer and place it in a bitmap. Reading the QR Code

DecodingOptions readOptions = new() {
PossibleFormats = new List<BarcodeFormat> { BarcodeFormat.QR_CODE },
TryHarder = true
};

Bitmap readQRCodeBitmap = new(imageFileName);
BarcodeReader reader = new() {
Options = readOptions
};
Result qrCodeResult = reader.Decode(readQRCodeBitmap);

if (qrCodeResult != null)
Console.WriteLine(qrCodeResult.Text);

Another ways

string imageFileName = "klc_qr_code.png";

QrCodeEncodingOptions options = new() {
DisableECI = true,
CharacterSet = "UTF-8",
Width = 500,
Height = 500
};

BarcodeWriter writer = new() {
Format = BarcodeFormat.QR_CODE,
Options = options
};

var contactInfo = new StringBuilder();
contactInfo.Append("MECARD:");
contactInfo.Append("N:Elzerman,Kenji;");
contactInfo.Append("TEL:123456789;");
contactInfo.Append("EMAIL:[email protected];");
contactInfo.Append("ADR:123 Main St., Anytown, USA;");
contactInfo.Append("NOTE:Phonenumber and address are fake!");

Bitmap qrCodeBitmap = writer.Write(contactInfo.ToString());
qrCodeBitmap.Save(imageFileName);
2
Ton Plooij On

By definition, you can't create or generate QR codes drawn with circles instead of shapes (although you can of course create images that have patterns similar to QR codes but with different shapes, but those would be unreadable by a QR code scanner). The three corner squares (known as Finder Patterns) are defined by ISO 18004 as 'three superimposed concentric squares'. Similar for alignment patterns in QR code symbols version 2 and up.

1
CSharp On

You might have to play around with the numbers to get the correct combination.

This is .NET Framework 4.8 Console App.

Nuget package: NuGet\Install-Package QRCoder

using System;
using System.IO;
using QRCoder;
using System.Drawing;

namespace QR
{
    internal class Program
    {
        private static readonly string QrText = "QR";
        private static readonly string SavePath = @"D:\Images";

        private static void Main()
        {
            Bitmap qrImage = GenerateQrImage(QrText);

            if (qrImage == null)
            {
                Print("Failed to generate QR code.");
            }
            else
            {
                Print("QR code generated successfully. Saving the image...");
                bool res = SaveImage(qrImage, SavePath);

                if (res)
                {
                    Print("Image saved successfully.");
                }
                else
                {
                    Print("Failed to save the image.");
                }
            }
        }

        private static Bitmap GenerateQrImage(string qrText)
        {
            try
            {
                QRCodeGenerator qrGen = new QRCodeGenerator();
                QRCodeData qrData = qrGen.CreateQrCode(qrText, QRCodeGenerator.ECCLevel.Q);
                QRCode qrCode = new QRCode(qrData);
                Bitmap qrBmp = qrCode.GetGraphic(20, Color.Blue, Color.White, true);

                using (Graphics graphics = Graphics.FromImage(qrBmp))
                {
                    graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                    int size = qrBmp.Width;
                    int moduleCount = qrData.ModuleMatrix.Count;
                    int scale = size / moduleCount;
                    int finderPatternSize = 10 * scale;

                    DrawFinderPatternCircleStyle(graphics, 0, 0, finderPatternSize, Color.Blue);
                    DrawFinderPatternCircleStyle(graphics, (moduleCount - 7) * scale, 0, finderPatternSize, Color.Blue);
                    DrawFinderPatternCircleStyle(graphics, 0, (moduleCount - 7) * scale, finderPatternSize, Color.Blue);

                    for (int y = 0; y < moduleCount; y++)
                    {
                        for (int x = 0; x < qrData.ModuleMatrix[y].Length; x++)
                        {
                            if (IsFinderPattern(x, y, moduleCount))
                                continue;

                            if (qrData.ModuleMatrix[y][x])
                            {
                                graphics.FillEllipse(Brushes.Blue, x * scale, y * scale, scale, scale);
                            }
                        }
                    }
                }

                return qrBmp;
            }
            catch (Exception ex)
            {
                Print(ex);
                return null;
            }
        }

        private static bool IsFinderPattern(int x, int y, int matrixSize)
        {
            int patternSize = 9;
            if (x < patternSize && y < patternSize) return true;
            if (x > matrixSize - patternSize && y < patternSize) return true;
            if (x < patternSize && y > matrixSize - patternSize) return true;
            return false;
        }

        private static void DrawFinderPatternCircleStyle(Graphics graphics, int x, int y, int circleDiameter, Color color)
        {
            int whiteCircleDiameter = circleDiameter * 5 / 7;
            int whiteCircleOffset = circleDiameter / 7;
            int middleDotDiameter = circleDiameter * 3 / 7;
            int middleDotOffset = circleDiameter * 2 / 7;

            graphics.FillEllipse(new SolidBrush(color), x, y, circleDiameter, circleDiameter);
            graphics.FillEllipse(Brushes.White, x + whiteCircleOffset, y + whiteCircleOffset, whiteCircleDiameter, whiteCircleDiameter);
            graphics.FillEllipse(new SolidBrush(color), x + middleDotOffset, y + middleDotOffset, middleDotDiameter, middleDotDiameter);
        }

        private static bool SaveImage(Bitmap qrImage, string savePath)
        {
            try
            {
                if (!Directory.Exists(savePath))
                {
                    Directory.CreateDirectory(savePath);
                }

                string fileName = $"{DateTime.Now:yyyyMMddHHmmss}.png";
                qrImage.Save(Path.Combine(savePath, fileName), System.Drawing.Imaging.ImageFormat.Png);

                return true;
            }
            catch (Exception ex)
            {
                Print(ex);
                return false;
            }
        }

        private static void Print(Exception ex)
        {
            try
            {
                string err = ex.ToString().Trim();
                Print(err);
            }
            catch (Exception)
            {
                Print("An unknown error occurred.");
            }
        }

        private static void Print(string text)
        {
            try
            {
                Console.WriteLine(text.Trim());
            }
            catch (Exception)
            { }
        }
    }
}