1. 程式人生 > >C#並口熱敏小票印表機列印點陣圖

C#並口熱敏小票印表機列印點陣圖

最近一直在研究並口小票印表機列印圖片問題,這也是第一次和硬體打交道,不過還好,最終成功了。

這是DEMO的窗體:

下面是列印所需要呼叫的程式碼:

class LptControl
    {
        private string LptStr = "lpt1";
        public LptControl(string l_LPT_Str)
        {
           
            LptStr = l_LPT_Str;
        }
        [StructLayout(LayoutKind.Sequential)]
        private struct OVERLAPPED
        {
            int Internal;
            int InternalHigh;
            int Offset;
            int OffSetHigh;
            int hEvent;
        }
    
     
        //呼叫DLL.
        [DllImport("kernel32.dll")]
        private static extern int CreateFile(string lpFileName, uint dwDesiredAccess, int dwShareMode, int lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, int hTemplateFile);
        [DllImport("kernel32.dll")]
        private static extern bool WriteFile(int hFile, byte[] lpBuffer, int nNumberOfBytesToWrite, ref int lpNumberOfBytesWritten, ref OVERLAPPED lpOverlapped);
        [DllImport("kernel32.dll")]
        private static extern bool CloseHandle(int hObject);
        private int iHandle;
        
        
        /// <summary>
        /// 開啟埠
        /// </summary>
        /// <returns></returns>
        public bool Open()
        {
            iHandle = CreateFile(LptStr, 0x40000000, 0, 0, 3, 0, 0);
            // iHandle = CreateFile(LptStr, GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

            if (iHandle != -1)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// 列印字串,通過呼叫該方法可以列印需要的字串
        /// </summary>
        /// <param name="Mystring"></param>
        /// <returns></returns>
        public bool Write(String Mystring)
        {
             //如果埠為開啟,則提示,開啟,則列印
            if (iHandle != -1)
            {
                OVERLAPPED x = new OVERLAPPED();
                int i = 0;
                //byte[] mybyte = System.Text.Encoding.Default.GetBytes(Mystring);
                byte[] mybyte = Encoding.GetEncoding("GB2312").GetBytes(Mystring);
                bool b = WriteFile(iHandle, mybyte, mybyte.Length, ref i, ref x);
                return b;
            }
            else
            {
                throw new Exception("不能連線到印表機!");
            }
        }
        /// <summary>
        /// 列印命令,通過引數,可以列印小票印表機的一些命令,比如換行,行間距,列印點陣圖等。
        /// </summary>
        /// <param name="mybyte"></param>
        /// <returns></returns>
        public bool Write(byte[] mybyte)
        {
            //如果埠為開啟,則提示,開啟,則列印
            if (iHandle != -1)
            {
                OVERLAPPED x = new OVERLAPPED();
                int i = 0;
                return WriteFile(iHandle, mybyte, mybyte.Length, ref i, ref x);
            }
            else
            {
                throw new Exception("不能連線到印表機!");
            }
        }

        /// <summary>
        /// 關閉埠
        /// </summary>
        /// <returns></returns>
        public bool Close()
        {
            return CloseHandle(iHandle);
        }

}

因為我們這裡主要是列印條形碼和二維碼,所以以條形碼和二維碼為例,寫了一個小的呼叫程式(這裡把列印圖片的方法貼出來):

 /// <summary>
        /// 列印圖片方法
        /// </summary>
        public void PrintOne()
        {
            //獲取圖片
            Bitmap bmp = new Bitmap(pictureBox1.Image);

            //設定字元行間距為n點行
            //byte[] data = new byte[] { 0x1B, 0x33, 0x00 };
            string send = "" + (char)(27) + (char)(51) + (char)(0);
            byte[] data = new byte[send.Length];
            for (int i = 0; i < send.Length; i++)
            {
                data[i] = (byte)send[i];
            }
            lc.Write(data);

            data[0] = (byte)'\x00';
            data[1] = (byte)'\x00';
            data[2] = (byte)'\x00';    // Clear to Zero.

            Color pixelColor;


            //ESC * m nL nH d1…dk   選擇點陣圖模式
            // ESC * m nL nH
            byte[] escBmp = new byte[] { 0x1B, 0x2A, 0x00, 0x00, 0x00 };

            escBmp[2] = (byte)'\x21';

            //nL, nH
            escBmp[3] = (byte)(bmp.Width % 256);
            escBmp[4] = (byte)(bmp.Width / 256);

            //迴圈圖片畫素列印圖片
            //迴圈高
            for (int i = 0; i < (bmp.Height / 24 + 1); i++)
            {
                //設定模式為點陣圖模式
                lc.Write(escBmp);
                //迴圈寬
                for (int j = 0; j < bmp.Width; j++)
                {
                    for (int k = 0; k < 24; k++)
                    {
                        if (((i * 24) + k) < bmp.Height)  // if within the BMP size
                        {
                            pixelColor = bmp.GetPixel(j, (i * 24) + k);
                            if (pixelColor.R == 0)
                            {
                                data[k / 8] += (byte)(128 >> (k % 8));

                            }
                        }
                    }
                    //一次寫入一個data,24個畫素
                    lc.Write(data);

                    data[0] = (byte)'\x00';
                    data[1] = (byte)'\x00';
                    data[2] = (byte)'\x00';    // Clear to Zero.
                }

                //換行,列印第二行
                byte[] data2 = { 0xA };
                lc.Write(data2);
            } // data
            lc.Write("\n\n");
        }

在列印過程中,出現一個比較低階的錯誤,因為小票印表機是並口的,而我電腦是串列埠的,所以一直遠端在另一臺電腦上測試,所以打印出來的圖片中間多了一條橫線,這個問題解決了多半天,因為我一直考慮到是列印圖片中可能少一層迴圈的問題,所以順便把列印圖片的原理整理了一下(之前的迴圈是從網上找到的,感覺應該沒問題就沒有細研究)。下面分享一下我的理解:

這是列印點陣圖的命令(每一個印表機都會給出這樣的說明,可以直接下載到的):

1.  ESC* m nL nH d1…dk   選擇點陣圖模式

格式:   ASCII: ESC * m nL nH d1…dk

      十進位制:  [27] [42] m nL nH d1…dk

    十六進位制:  [1BH][2AH] m nL nH d1…dk

說明:

    .設定點陣圖方式(用m)、點數(用nL,nH)以及點陣圖內容(用dk)。

    .m=0,1,32,33;0≤nL≤255,0≤nH≤3,0≤d≤255。

     k=nL+nH×256(m=0,1);k=(nL+nH×256)×3(m=32,33)。

    .水平方向點數為(nL+nH×256)。

    .如果點數超過一行,超過其最大點數(與選擇的點陣圖方式有關,詳      見下表)的部分被忽略。

    .d為點陣圖資料位元組,對應位為1則表示該點列印,對應位為0,則  表示該點不列印。(k表示資料個數)

    .m用於選擇點陣圖方式。

模式

縱向

橫向

點數

解析度

解析度

資料個數(k)

0

8點單密度

8

67  DPI

100  DPI

nL+nH×256

1

8點雙密度

8

67  DPI

200  DPI

nL+nH×256

32

24點單密度

24

200  DPI

100  DPI

(nL+nH×256)×3

33

24點雙密度

24

200  DPI

200  DPI

(nL+nH×256)×3

這次用的印表機列印是24點雙密度的,所以我這裡就只解釋下m=33的情況。

從程式碼中可以看出,列印圖片過程主要是通過迴圈一點點列印的,通過

lc.Write(data);

迴圈寫入,當然前面的lc.Write(escBmp)主要是些ESC * m三個引數很容易理解就不多解釋了。而data是一個長度為3byte陣列,這個data在列印中起到什麼作用呢?

在印表機m=33的模式縱向每次是列印24個點,也就是說,而byte8個位元組,所以需要3byte型別的樹才能完成模式為24點雙密碼的點陣圖列印方式,通過三個字元來平湊一個畫素寬24個畫素長的圖片,然後迴圈寬度,來列印圖片寬度大小24個畫素高度的圖片,在通過每次迴圈24個畫素的高度,最終打印出完成的圖片。

需要列印的圖片:

第一次迴圈先是高位24畫素

然後把寬度分解開,迴圈每一畫素的寬度,然後列印每一畫素寬度的圖片:

舉個例子,假設陣列data[d1,d2,d3],d1= 00000111,d2=11111111,d3 =11111111,所以打印出的一個畫素寬,24畫素高的圖片為:

最終通過迴圈寬度與高度,把最終的點陣圖畫出來。

這裡我舉的是24點密度的例子,通過,如果您有興趣研究的話,也經常看到這樣的程式碼:

    for (int i = 0; i < ((bmp.Height + 7) / 8); i++)
                {
                    _serialPort.Write(escBmp, 0, escBmp.Length);

                    for (int j = 0; j < bmp.Width; j++)
                    {
                        for (int k = 0; k < 8; k++)
                        {
                            if (((i * 8) + k) < bmp.Height)  // if within the BMP size
                            {
                                pixelColor = bmp.GetPixel(j, (i * 8) + k);
                                if (pixelColor.R == 0)
                                {
                                    data[0] += (byte)(128 >> k);
                                }
                            }
                        }

                        _serialPort.Write(data, 0, 1);
                        data[0] = (byte)'\x00'; // Clear to Zero.
                    }

這個很明顯就是8點密度的模式,所以他的data長度為1,即需要8個位元組就夠了。

打印出的效果還是很不錯的。