用Zebra打印機制作一個節日賀卡


​寫在前面

今天的主題就是用Zebra斑馬打印機打印一個賀卡標簽。

Zebra介紹

既然是賀卡,應該是非常漂亮的,但是這個Zebra打印機好像只能打印黑白的,所以就簡單制作了一下。

工業上在批量打印商品標簽時,一般都要加上條碼或圖片,一般使用Zebra斑馬打印機比較多。而C#調用斑馬打印機的方式也有很多種,包括串口COM、以太網TCP、並口LPT以及USB等,對於設計標簽的方式也有很多種方式,比如Zebra提供了一個設計軟件Zebra Designer,還可以使用第三方軟件如Bartender,當然我們還可以自己通過GDI+技術進行繪制。

本例采用的方式是利用GDI+進行繪制,然后將圖像轉換成字節,通過DG和XG指令發送給打印機,通信使用的是打印機自帶的USB接口。

圖像繪制

首先通過GDI+繪制一張圖像,圖像的大小要根據實際標簽大小進行調試,繪制內容可以是圖像、字符串等,或者是條形碼、二維碼都可以,其實條形碼及二維碼也是屬於圖像。

        private void btn_Paint_Click(object sender, EventArgs e)
        {
            //開始繪制圖片
            int imageWidth = 900;
            int imageHeight = 600;
            bitmap = new Bitmap(imageWidth, imageHeight);
            //創建Graphics
            Graphics graphics = Graphics.FromImage(bitmap);
            //呈現質量
            graphics.SmoothingMode = SmoothingMode.AntiAlias;
            //背景色
            graphics.Clear(Color.White);
            //字體
            Font myFont = new Font("楷體", 32, FontStyle.Bold);
            //文字顏色
            Brush brush = new SolidBrush(Color.Black);
            //調整間距
            int start = 145;       
            int gap = 80;
            graphics.DrawString("祝全天下的母親——母親節快樂", myFont, brush, 50, 50);
            graphics.DrawString("《游子吟》【唐】孟郊", myFont, brush, 50, start);
            graphics.DrawString("慈母手中線,游子身上衣", myFont, brush, 50, start + gap);
            graphics.DrawString("臨行密密縫,意恐遲遲歸", myFont, brush, 50, start + gap * 2);
            graphics.DrawString("誰言寸草心,報得三春暉", myFont, brush, 50, start + gap * 3);
            graphics.DrawString("署名:" + this.txt_Name.Text.Trim(), myFont, brush, 50, start + gap * 4 + 20);
            //生成二維碼
            Image codeImage = BarCodeHelper.GenerateQRCode("https://ke.qq.com/course/301616", 220, 220);
            Bitmap pbitmap = new Bitmap(codeImage);
            pbitmap.MakeTransparent(Color.Transparent);
            graphics.DrawImage(pbitmap, 630, 338, 220, 220);
            //顯示圖形
            this.mainPic.Image = bitmap;
        }

編寫好代碼之后,將圖像用一個PictureBox控件顯示出來,結果如下:

 

圖像處理

生成圖像之后,接着將圖像轉換成字節數組或者字符串,便於后續直接發送給打印機,這里的代碼是在網上找的,

    public static string BitmapToHex(Image sourceBmp, out int totalBytes, out int rowBytes)
        {
            // 轉成單色圖
            Bitmap grayBmp = ConvertToGrayscale(sourceBmp as Bitmap);
            // 鎖定位圖數據    
            Rectangle rect = new Rectangle(0, 0, grayBmp.Width, grayBmp.Height);
            BitmapData bmpData = grayBmp.LockBits(rect, ImageLockMode.ReadWrite, grayBmp.PixelFormat);
            // 獲取位圖數據第一行的起始地址     
            IntPtr ptr = bmpData.Scan0;
            // 定義數組以存放位圖的字節流數據      
            // 處理像素寬對應的字節數,如不為8的倍數,則對最后一個字節補0    
            int width = (int)Math.Ceiling(grayBmp.Width / 8.0);
            // 獲取位圖實際的字節寬,這個值因為要考慮4的倍數關系,可能大於width  
            int stride = Math.Abs(bmpData.Stride);
            // 計算位圖數據實際所占的字節數,並定義數組      
            int bitmapDataLength = stride * grayBmp.Height;
            byte[] ImgData = new byte[bitmapDataLength];
            // 從位圖文件復制圖像數據到數組,從實際圖像數據的第一行開始;因ptr指針而無需再考慮行倒序存儲的處理          
            System.Runtime.InteropServices.Marshal.Copy(ptr, ImgData, 0, bitmapDataLength);
            // 計算異或操作數,以處理包含圖像數據但又有補0操作的那個字節         
            byte mask = 0xFF;
            // 計算這個字節補0的個數       
            //int offset = 8 * width - grayBmp.Width;
            int offset = 8 - (grayBmp.Width % 8);
            //offset %= 8;
            offset = offset % 8;
            // 按補0個數對0xFF做相應位數的左移位操作           
            mask <<= (byte)offset;
            // 圖像反色處理        
            for (int j = 0; j < grayBmp.Height; j++)
            {
                for (int i = 0; i < stride; i++)
                {
                    if (i < width - 1) //無補0的圖像數據
                    {
                        ImgData[j * stride + i] ^= 0xFF;
                    }
                    else if (i == width - 1) //有像素的最后一個字節,可能有補0   
                    {
                        ImgData[j * stride + i] ^= mask;
                    }
                    else  //為滿足行字節寬為4的倍數而最后補的字節        
                    {
                        ImgData[j * stride + i] ^= 0x00;
                    }
                }
            }
            // 將位圖數據轉換為16進制的ASCII字符          
            string zplString = BitConverter.ToString(ImgData);
            zplString = CompressLZ77(zplString.Replace("-", string.Empty));
            totalBytes = bitmapDataLength;
            rowBytes = stride;
            return zplString;
        }

調用打印機

調用打印機使用的是winspool.drv,這個庫里提供了很多操作打印機的方法。

            [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
            [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool ClosePrinter(IntPtr hPrinter);
            [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
            [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool EndDocPrinter(IntPtr hPrinter);
            [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool StartPagePrinter(IntPtr hPrinter);
            [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool EndPagePrinter(IntPtr hPrinter);
            [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
            public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

基於這些方法,封裝一個將字符串發送給打印機的方法:

            public  bool SendStringToPrinter(string szPrinterName, string szString)
            {
                try
                {
                    IntPtr pBytes;
                    Int32 dwCount;
                    // 獲取字符串長度  
                    dwCount = szString.Length;
                    // 將字符串復制到非托管 COM 任務分配的內存非托管內存塊,並轉換為 ANSI 文本
                    pBytes = Marshal.StringToCoTaskMemAnsi(szString);
                    // 將已轉換的 ANSI 字符串發送到打印機
                    bool res = SendBytesToPrinter(szPrinterName, pBytes, dwCount);
                    // 釋放先前分配的非托管內存
                    Marshal.FreeCoTaskMem(pBytes);
                    return res;
                }
                catch (Win32Exception ex)
                {
                    WriteLog(ex.Message);
                    return false;
                }
            }

最后在打印按鈕下,組織相關命令,調用這個方法即可:

        private void btn_Print_Click(object sender, EventArgs e)
        {
            int total = 0;
            int row = 0;
            string hex = ZebraUnity.BitmapToHex(bitmap, out total, out row);                    
            string modecmd = "~DGR:ZLOGO.GRF," + total.ToString() + "," + row.ToString() + "," + hex;//將圖片生成模板指令
            ZebraGesigner.PrintCode.SendStringToPrinter("ZDesigner ZT210", modecmd);
            string cmd = "^XA^FO0,0^XGR:ZLOGO.GRF,1,1^FS^XZ";//調用該模板指令
            ZebraGesigner.PrintCode.SendStringToPrinter("ZDesigner ZT210", cmd);//發送調用模板指令
        }

最終結果

下面這個就是最終打印的結果:

 

小彩蛋:你們掃掃二維碼看看是什么呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM