Search code examples
c#qr-codezxing

How can I generate circular QR codes?


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


Solution

  • 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);
        }
    }