简体   繁体   中英

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

I've come across this problem many times over the years and still live in hope that there is an easy way to do this that I have missed. I work with barcodes a lot. They are usually made of black dots or lines on a white background. Barcode readers generally work faster and more accurately when the edges are crisp and then size of the lines or dots are precise.

Most barcode generation algorithms will give you a compact barcode usually with the smallest element size being one pixel. A typical QR code could fit in a 21 x 21 grid. This would be too small to see if printed pixel to pixel on most printers and would typically be scaled up. The result of scaling it up depends on the method used and although sometimes you are given a choice, often you have no options that make the image suitable. Even printing directly will often give you expected gray artefacts or forms of dithering. The most consistent way I have found is to scale the images before they are use daily in other places such as Microsoft Word, lightburn and a few others I use that still give me a headache.

Below I will go through what I have tried and show the results. I am limiting this to bitmaps only because using vectors here is not something I need on my current project.

My current best resolution is not pretty, it is slow and although I could improve the speed by locking the bits in the bitmap, I am hoping someone has a really simple answer that I had totally missed on my search again this time.

Here is an image of a simple QR code blown up in GIMP.

二维码图片

The problem is, if it is scaled up, it'll often end up looking like this: 缩放后的图像

Below I created a small test program to go through all the different modes I know of and then generate a matrix of images which I have reproduced below. The version I currently use is Mode 99 which involves inspecting each pixel and drawing a square.

Does anyone have any better ideas?

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

使用测试程序渲染的最终图像数组

You can use a library like ImageTracer.NET to convert the image to a vector image, then it'll scale as big as you need:

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

This is how I resize a Bitmap in one shot with DrawImage and nearest neighbor sampling.

In your case, I'd prepare the original bitmap with one pixel per logical dot with respect to the geometric dimensions of your pattern (eg at 21 x 21) Then I'd resize that bitmap to a printable/displayable output format using Interpolationmode.NearestNeighbor sampling using the code I'm providing below.

It's important to set PixelOffsetMode.HighQuality . (Unless you want to adjust your coordinates by -0.5f, -0.5f, which is just annoying, so don't bother).

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

In the first place, I find it weird that your generators don't just allow you to specify the cell size/modulus. Double check the manual(s).

To obtain what you want from single pixel cells/modulus, you can use any image magnification function with a size that is an exact multiple of your original image, using the nearest neighbor resampling rule, and no other.

If you can't find such a function, it is an easy matter to write one that copies an image while replicating every pixel as an NxN square (under lockbits).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM