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

下面是打印所需要調用的代碼:
因為我們這里主要是打印條形碼和二維碼,所以以條形碼和二維碼為例,寫了一個小的調用程序(這里把打印圖片的方法貼出來):
/// <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個字節就夠了。
打印出的效果還是很不錯的。

如果大家有興趣研究網絡打印,請參加小崔的博客:http://blog.csdn.net/xiaoxian8023/article/details/8440625#comments
