在 C# 中放大条形码图像的最佳无损方式是什么

What is the best lossless way to scale up a barcode image in c#

多年来我多次遇到这个问题,但仍然希望有一种我错过的简单方法可以解决这个问题。 我经常使用条形码。它们通常由白色背景上的黑点或线条组成。当边缘清晰并且线条或点的大小精确时,条形码阅读器通常工作得更快、更准确。

大多数条形码生成算法都会为您提供一个紧凑的条形码,通常最小元素大小为一个像素。典型的 QR 码可以放入 21 x 21 的网格中。这太小了,无法查看是否在大多数打印机上打印像素到像素,并且通常会按比例放大。放大它的结果取决于使用的方法,虽然有时你可以选择,但通常你没有使图像合适的选项。即使是直接打印,通常也会出现预期的灰色伪像或抖动形式。我发现最一致的方法是在图像每天在其他地方使用之前缩放图像,例如 Microsoft Word、lightburn 和其他一些我使用的仍然让我头疼的地方。

下面我将介绍我尝试过的内容并展示结果。我将其限制为位图,因为我当前的项目不需要在此处使用矢量。

我目前的最佳分辨率不是很好,它很慢,虽然我可以通过锁定位图中的位来提高速度,但我希望有人有一个非常简单的答案,我再次搜索时完全错过了这个时间.

这是在 GIMP 中放大的简单 QR 码的图像。

问题是,如果按比例放大,通常会变成这样:

下面我创建了一个小测试程序来检查我所知道的所有不同模式,然后生成我在下面复制的图像矩阵。我目前使用的版本是模式 99,它涉及检查每个像素并绘制一个正方形。

有没有人有更好的主意?

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace PSWpfCommon.Images
{
    public partial class WPFImageHelper
    {

        public class Test
        {
            public int smode = 0;
            public int imode = 0;
            public int mode = 0;
            public string title = "";
            public Test(int s, int i, int m, string t)
            {
                smode = s;
                imode = i;
                mode = m;
                title = t;
            }
        }

        public static Bitmap TestImage()
        {
            byte[] img =
             {
           0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44,
           0x52, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x08, 0x06, 0x00, 0x00, 0x00, 0xA9,
           0x17, 0xA5, 0x96, 0x00, 0x00, 0x00, 0xCF, 0x49, 0x44, 0x41, 0x54, 0x38, 0xCB, 0x9D, 0x54,
           0x5B, 0x0E, 0xC3, 0x30, 0x08, 0xB3, 0xAB, 0xDC, 0xFF, 0xCA, 0xDE, 0xC7, 0xD4, 0x88, 0x7A,
           0x3C, 0xD2, 0x21, 0x55, 0xAD, 0x48, 0xA0, 0xC6, 0x80, 0x09, 0x40, 0x28, 0x4C, 0x12, 0x48,
           0xEE, 0x6F, 0x00, 0x20, 0xF9, 0xF0, 0x67, 0xB6, 0x62, 0x40, 0xB4, 0x98, 0xAC, 0x4A, 0x50,
           0xC5, 0x2D, 0x4F, 0xE2, 0x97, 0x23, 0xB2, 0xEE, 0xE7, 0x31, 0xEE, 0xC2, 0xA1, 0x75, 0x89,
           0xD3, 0xF2, 0x27, 0x73, 0x5E, 0x8F, 0x93, 0x56, 0x01, 0x53, 0xA2, 0xEC, 0x7C, 0x39, 0x2F,
           0x19, 0xCA, 0x58, 0x7A, 0xA4, 0xA0, 0x8A, 0xA3, 0x0E, 0x6A, 0x7A, 0x5B, 0xFE, 0x45, 0x12,
           0xF1, 0xF1, 0x44, 0x59, 0x73, 0xFC, 0x5E, 0x8C, 0x25, 0xF9, 0xED, 0xBE, 0xA4, 0x47, 0x49,
           0xDD, 0x18, 0x79, 0xF9, 0x25, 0xA7, 0xD9, 0xA6, 0x74, 0xE3, 0xE3, 0x48, 0x9D, 0xE3, 0x55,
           0xA1, 0x98, 0xB8, 0xCC, 0x16, 0xE4, 0xF6, 0x5F, 0xBE, 0xDF, 0x8E, 0x74, 0x42, 0x9F, 0x4D,
           0xC0, 0x0F, 0xA7, 0xFE, 0x76, 0x14, 0x5D, 0x65, 0xDB, 0x3F, 0xA9, 0x54, 0xC7, 0x9D, 0x8B,
           0xCD, 0x7D, 0x3E, 0xAA, 0xD4, 0x24, 0x77, 0x99, 0x7F, 0x54, 0xA9, 0xAA, 0x69, 0x7E, 0x3F,
           0xCE, 0xEA, 0xFA, 0x67, 0x9B, 0x3A, 0x2A, 0x24, 0x9D, 0x49, 0x9F, 0x23, 0x99, 0x64, 0x71,
           0x9D, 0xA8, 0x51, 0xC5, 0x6F, 0x36, 0x21, 0x5B, 0xF9, 0x3B, 0x95, 0xAA, 0x1A, 0x52, 0xAD,
           0xB1, 0x24, 0x7C, 0x00, 0x22, 0x8E, 0xDE, 0x4C, 0xC4, 0x8F, 0x11, 0x7F, 0x00, 0x00, 0x00,
           0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
             };

            Bitmap qr;
            using (var ms = new MemoryStream(img))
            {
                qr = new Bitmap(ms);
            }

            int factor = 4;

            var l = new List<Test>();
            for (int i = 0; i <= 7; i++)
                for (int s = 0; s <= 4; s++)
                    l.Add(new Test(s, i, 0, $"s={s} i={i}"));

            l.Add(new Test(2, 8, 99, $"Mode 99"));


            Bitmap fullimage = new Bitmap(((qr.Width * factor) + 20) * 5, ((qr.Height * factor) + 20) * 9);


            fullimage.SetResolution(72, 72);
            var font = new Font("Arial", 10);

            using (var grPhoto = Graphics.FromImage(fullimage))
            using (var blackbrush = new SolidBrush(System.Drawing.Color.Black))
            using (var whitebrush = new SolidBrush(System.Drawing.Color.White))
            {
                grPhoto.InterpolationMode = InterpolationMode.High;

                grPhoto.FillRectangle(whitebrush, 0, 0, fullimage.Width, fullimage.Height);

                foreach (var t in l)
                {
                    var newqr = GrowImage(qr, factor: 4, mode: t.mode, imode: t.imode, smode: t.smode);

                    grPhoto.DrawImage(newqr,
                        t.smode * ((qr.Width * factor) + 20),
                        t.imode * ((qr.Height * factor) + 20));
                    grPhoto.DrawString(t.title, font, blackbrush,
                        t.smode * ((qr.Width * factor) + 20),
                        (t.imode + 1) * ((qr.Height * factor) + 20) - 20);

                }
            }

            fullimage.Save(@"c:\temp\newqr.png", ImageFormat.Png);

            return null;
        }

        public static Bitmap GrowImage(Bitmap im, int factor = 4, int mode = 1, int imode = 0, int smode = 0, int border = 2)
        {
            bool translate = true;

            var bmPhoto = new Bitmap(im.Width * factor + 2 * border, im.Height * factor + border * 2, PixelFormat.Format24bppRgb);

            bmPhoto.SetResolution(72, 72);

            using (var grPhoto = Graphics.FromImage(bmPhoto))
            using (var blackbrush = new SolidBrush(System.Drawing.Color.Black))
            using (var whitebrush = new SolidBrush(System.Drawing.Color.White))
            {
                grPhoto.FillRectangle(whitebrush, 0, 0, bmPhoto.Width, bmPhoto.Height);

                switch (smode)
                {
                    case 0:
                        grPhoto.SmoothingMode = SmoothingMode.Default;
                        break;
                    case 1:
                        grPhoto.SmoothingMode = SmoothingMode.AntiAlias;
                        break;
                    case 2:
                        grPhoto.SmoothingMode = SmoothingMode.HighQuality;
                        break;
                    case 3:
                        grPhoto.SmoothingMode = SmoothingMode.HighSpeed;
                        break;
                    case 4:
                        grPhoto.SmoothingMode = SmoothingMode.None;
                        break;
                    default:
                        break;
                }
                switch (imode)
                {
                    case 0:
                        grPhoto.InterpolationMode = InterpolationMode.Default;
                        break;
                    case 1:
                        grPhoto.InterpolationMode = InterpolationMode.Bicubic;
                        break;
                    case 2:
                        grPhoto.InterpolationMode = InterpolationMode.Bilinear;
                        break;
                    case 3:
                        grPhoto.InterpolationMode = InterpolationMode.High;
                        break;
                    case 4:
                        grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        break;
                    case 5:
                        grPhoto.InterpolationMode = InterpolationMode.HighQualityBilinear;
                        break;
                    case 6:
                        grPhoto.InterpolationMode = InterpolationMode.Low;
                        break;
                    case 7:
                        grPhoto.InterpolationMode = InterpolationMode.NearestNeighbor;
                        break;
                    default:
                        break;
                }

                switch (mode)
                {
                    case 99:
                        // These are what worked best for me...
                        grPhoto.SmoothingMode = SmoothingMode.None;
                        grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;


                        for (int x = 0; x < im.Width; x++)
                            for (int y = 0; y < im.Height; y++)
                            {
                                var g = im.GetPixel(x, y);
                                if (g.R < 120 && g.B < 120) // Just being really lazy here... if the pixel has not much blue and not much red I'll treat it as black
                                {
                                    grPhoto.FillRectangle(blackbrush, border + factor * x, border + factor * y, factor, factor);

                                }
                            }
                        translate = false;
                        break;
                    default:
                        break;
                }

                if (translate) // If we used mode 99, don't draw the image
                    grPhoto.DrawImage(im, new System.Drawing.Rectangle(border, border, im.Width * factor, im.Height * factor), 0, 0, im.Width, im.Height, System.Drawing.GraphicsUnit.Pixel);
            }

            return bmPhoto;
        }
    }
}

您可以使用 ImageTracer.NET 之类的库将图像转换为矢量图像,然后它会根据需要缩放:

https://github.com/MiYanni/ImageTracer.NET

首先,我觉得您的生成器不仅仅允许您指定单元格 size/modulus 很奇怪。仔细检查手册。

要从单个像素 cells/modulus 获得您想要的内容,您可以使用任何图像放大函数,其大小是原始图像的精确倍数,使用 最近邻 重采样规则,无其他。

如果找不到这样的函数,很容易编写一个复制图像的函数,同时将每个像素复制为 NxN 正方形(在锁定位下)。

这就是我使用 DrawImage 和最近邻采样一次性调整 Bitmap 大小的方法。

在您的情况下,我会根据您的图案的几何尺寸(例如 21 x 21)准备原始位图,每个逻辑点一个像素,然后我会将该位图调整为 printable/displayable 使用 Interpolationmode.NearestNeighbor 使用我在下面提供的代码进行采样的输出格式。

设置PixelOffsetMode.HighQuality很重要。 (除非你想调整你的坐标-0.5f,-0.5f,这很烦人,所以不要打扰)。

public static Bitmap Resize(this Bitmap oldBitmap, int newWidth, int newHeight)
{
    Bitmap newBitmap = new Bitmap(newWidth, newHeight, oldBitmap.PixelFormat);
    using (Graphics g = Graphics.FromImage(newBitmap)) {
        RectangleF dst = new RectangleF(0, 0, newWidth, newHeight);
        RectangleF src = new RectangleF(0, 0, oldBitmap.Width, oldBitmap.Height);
        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
        g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        g.DrawImage(oldBitmap, dst, src, GraphicsUnit.Pixel);
    }
    return newBitmap;
}