manuelbl/QrCodeGenerator

How to generate correct size of QR Code image?

Closed this issue · 2 comments

https://github.com/manuelbl/QrCodeGenerator/blob/master/Demo-ImageSharp/Program.cs

        internal static void HelloWorld()
        {
            var text = "Hello, world!";
            var filename = "hello-world-QR.png";

            var qr = QrCode.EncodeText(text, QrCode.Ecc.High); // Create the QR code symbol
            int border = 4;
            int scale = (int)(128 * 1.0 / (qr.Size + border * 2));
            qr.SaveAsPng(filename, scale, border);

            Console.WriteLine($"The QR code has been saved as {Path.GetFullPath(filename)}");
        }

I expect output size of the QR Code image is 128px.
But the generated size of QR Code image is 99px.
hello-world-QR

While the target size is 128px, the scaling factor is rounded down to an integer number:

int scale = (int)(128 * 1.0 / (qr.Size + border * 2));

So it becomes 3 instead of 3.878, resulting in an image of 99px by 99px instead of the target 128px by 128px.

Integer numbers for scaling are used so the QR code pixels (aka modules) perfectly align with the image pixels. Unless a high resolution with a high scaling factor is used, non-integer scaling factors would either lead to blurry pixels (if anti-aliasing is used) or to uneven pixel width and height (if anti-aliasing is not used).

Below you can find an alternative QrCodeBitmapExtension.cs implementation using a floating-point instead of an integer scaling factor.

In the main program, the scaling factor line must be changed as well:

float scale = 128f / (qr.Size + border * 2);

The result is:

hello-world-QR

Note the slightly blurry module edges and what looks like a fine grid on top of black pixels.

//
// QR code generator library (.NET)
// https://github.com/manuelbl/QrCodeGenerator
//
// Copyright (c) 2021 Manuel Bleichenbacher
// Licensed under MIT License
// https://opensource.org/licenses/MIT
//

using Net.Codecrete.QrCodeGenerator;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.IO;

namespace Net.Codecrete.QrCodeGenerator
{
    public static class QrCodeBitmapExtensions
    {
        /// <inheritdoc cref="ToBitmap(QrCode, int, int)"/>
        /// <param name="background">The background color.</param>
        /// <param name="foreground">The foreground color.</param>
        public static Image ToBitmap(this QrCode qrCode, float scale, int border, Color foreground, Color background)
        {
            // check arguments
            if (scale <= 0)
            {
                throw new ArgumentOutOfRangeException(nameof(scale), "Value out of range");
            }
            if (border < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(border), "Value out of range");
            }

            int size = qrCode.Size;
            int dim = (int)((size + border * 2) * scale);

            if (dim > short.MaxValue)
            {
                throw new ArgumentOutOfRangeException(nameof(scale), "Scale or border too large");
            }

            // slightly modify scaling factor to adapt to integer image size
            scale = dim / (float)(size + border * 2);

            // create bitmap
            Image<Rgb24> image = new Image<Rgb24>(dim, dim);

            image.Mutate(img =>
            {
                // draw background
                img.Fill(background);

                // draw modules
                for (int y = 0; y < size; y++)
                {
                    for (int x = 0; x < size; x++)
                    {
                        if (qrCode.GetModule(x, y))
                        {
                            img.Fill(foreground, new RectangleF((x + border) * scale, (y + border) * scale, scale, scale));
                        }
                    }
                }
            });

            return image;
        }

        /// <summary>
        /// Creates a bitmap (raster image) of this QR code.
        /// <para>
        /// The <paramref name="scale"/> parameter specifies the scale of the image, which is
        /// equivalent to the width and height of each QR code module. Additionally, the number
        /// of modules to add as a border to all four sides can be specified.
        /// </para>
        /// <para>
        /// For example, <c>ToBitmap(scale: 10, border: 4)</c> means to pad the QR code with 4 white
        /// border modules on all four sides, and use 10&#xD7;10 pixels to represent each module.
        /// </para>
        /// <para>
        /// The resulting bitmap uses the pixel format <see cref="PixelFormat.Format24bppRgb"/>.
        /// If not specified, the foreground color is black (0x000000) und the background color always white (0xFFFFFF).
        /// </para>
        /// </summary>
        /// <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>
        /// <returns>The created bitmap representing this QR code.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="scale"/> is 0 or negative, <paramref name="border"/> is negative
        /// or the resulting image is wider than 32,768 pixels.</exception>
        public static Image ToBitmap(this QrCode qrCode, float scale, int border)
        {
            return qrCode.ToBitmap(scale, border, Color.Black, Color.White);
        }

        /// <inheritdoc cref="ToPng(QrCode, int, int)"/>
        /// <param name="background">The background color.</param>
        /// <param name="foreground">The foreground color.</param>
        public static byte[] ToPng(this QrCode qrCode, float scale, int border, Color foreground, Color background)
        {
            using Image image = qrCode.ToBitmap(scale, border, foreground, background);
            using MemoryStream ms = new MemoryStream();
            image.SaveAsPng(ms);
            return ms.ToArray();
        }

        /// <summary>
        /// Creates a PNG image of this QR code and returns it as a byte array.
        /// <para>
        /// The <paramref name="scale"/> parameter specifies the scale of the image, which is
        /// equivalent to the width and height of each QR code module. Additionally, the number
        /// of modules to add as a border to all four sides can be specified.
        /// </para>
        /// <para>
        /// For example, <c>ToPng(scale: 10, border: 4)</c> means to pad the QR code with 4 white
        /// border modules on all four sides, and use 10&#xD7;10 pixels to represent each module.
        /// </para>
        /// <para>
        /// If not specified, the foreground color is black (0x000000) und the background color always white (0xFFFFFF).
        /// </para>
        /// </summary>
        /// <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>
        /// <returns>The created bitmap representing this QR code.</returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="scale"/> is 0 or negative, <paramref name="border"/> is negative
        /// or the resulting image is wider than 32,768 pixels.</exception>
        public static byte[] ToPng(this QrCode qrCode, float scale, int border)
        {
            return qrCode.ToPng(scale, border, Color.Black, Color.White);
        }

        /// <inheritdoc cref="SaveAsPng(QrCode, string, int, int)"/>
        /// <param name="background">The background color.</param>
        /// <param name="foreground">The foreground color.</param>
        public static void SaveAsPng(this QrCode qrCode, string filename, float scale, int border, Color foreground, Color background)
        {
            using Image image = qrCode.ToBitmap(scale, border, foreground, background);
            image.SaveAsPng(filename);
        }

        /// <summary>
        /// Saves this QR code as a PNG file.
        /// <para>
        /// The <paramref name="scale"/> parameter specifies the scale of the image, which is
        /// equivalent to the width and height of each QR code module. Additionally, the number
        /// of modules to add as a border to all four sides can be specified.
        /// </para>
        /// <para>
        /// For example, <c>SaveAsPng("qrcode.png", scale: 10, border: 4)</c> means to pad the QR code with 4 white
        /// border modules on all four sides, and use 10&#xD7;10 pixels to represent each module.
        /// </para>
        /// <para>
        /// If not specified, the foreground color is black (0x000000) und the background color always white (0xFFFFFF).
        /// </para>
        /// </summary>
        /// <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>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="scale"/> is 0 or negative, <paramref name="border"/> is negative
        /// or the resulting image is wider than 32,768 pixels.</exception>
        public static void SaveAsPng(this QrCode qrCode, string filename, float scale, int border)
        {
            qrCode.SaveAsPng(filename, scale, border, Color.Black, Color.White);
        }
    }
}
float scale = 128f / (qr.Size + border * 2);

img.Fill(foreground, new Rectangle((x + border) * scale, (y + border) * scale, scale, scale));

//
        // 摘要:
        //     Initializes a new instance of the SixLabors.ImageSharp.Rectangle struct.
        //
        // 参数:
        //   x:
        //     The horizontal position of the rectangle.
        //
        //   y:
        //     The vertical position of the rectangle.
        //
        //   width:
        //     The width of the rectangle.
        //
        //   height:
        //     The height of the rectangle.
        public Rectangle(int x, int y, int width, int height);

We use the library, which is convenient to provide parameters such as size,border and difficult to provide scale.