在 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 之类的库将图像转换为矢量图像,然后它会根据需要缩放:
首先,我觉得您的生成器不仅仅允许您指定单元格 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;
}
多年来我多次遇到这个问题,但仍然希望有一种我错过的简单方法可以解决这个问题。 我经常使用条形码。它们通常由白色背景上的黑点或线条组成。当边缘清晰并且线条或点的大小精确时,条形码阅读器通常工作得更快、更准确。
大多数条形码生成算法都会为您提供一个紧凑的条形码,通常最小元素大小为一个像素。典型的 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 之类的库将图像转换为矢量图像,然后它会根据需要缩放:
首先,我觉得您的生成器不仅仅允许您指定单元格 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;
}