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用於選擇點陣圖方式。
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是一個長度為3的byte陣列,這個data在列印中起到什麼作用呢?
在印表機m=33的模式縱向每次是列印24個點,也就是說,而byte為8個位元組,所以需要3個byte型別的樹才能完成模式為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個位元組就夠了。
打印出的效果還是很不錯的。