上一篇 主要介紹了如何通過藍牙連接到打印機。這一篇,我們就介紹如何向打印機發送打印指令,來打印字符和圖片。
1. 構造輸出流
首先要明確一點,就是藍牙連接打印機這種場景下,手機是 Client 端,打印機是 Server 端。
在上一篇的最后,我們從 BluetoothSocket 得到了一個OutputStream。這里我們做一層包裝,得到一個OutputStreamWriter 對象:
OutputStreamWriter writer = new OutputStreamWriter(outputStream, "GBK");
這樣做主要是為了后面可以直接輸出字符串,不然只能輸出 int 或 byte 數據;
2. 常用打印指令
手機通過藍牙向打印機發送的都是純字節流,那么打印機如何知道該打印的是一個文本,還是條形碼,還是圖片數據呢?
初始化打印機 :

在每次打印開始之前要調用該指令對打印機進行初始化。向打印機發送這條指令對應的代碼就是:
protected void initPrinter() throws IOException { writer.write(0x1B); writer.write(0x40); writer.flush(); }打印文本:
沒有對應指令,直接輸出
protected void printText(String text) throws IOException { writer.write(text); writer.flush(); }設置文本對齊方式:

對應的發送指令的代碼:
/* 設置文本對齊方式 * @param align 打印位置 0:居左(默認) 1:居中 2:居右 * @throws IOException */ protected void setAlignPosition(int align) throws IOException { writer.write(0x1B); writer.write(0x61); writer.write(align); writer.flush(); }與初始化指令不同的是,這條指令帶有一個參數n。
換行和制表符:
直接輸出對應的字符:
protected void nextLine() throws IOException { writer.write("/n"); writer.flush(); } protected void printTab(int length) throws IOException { for (int i = 0; i < length; i++) { writer.write("/t"); } writer.flush(); }這兩個指令在打印訂單詳情的時候使用最多。尤其是制表符,可以讓每一列的文字對齊。
設置行間距:

n表示行間距為n個像素點,最大值256
protected void setLineGap(int gap) throws IOException { writer.write(0x1B); writer.write(0x33); writer.write(gap); writer.flush(); }這個指令在后面打印圖片的時候會用到。
3. 打印圖片
很多小票上面都會附上一個二維碼,用戶掃描之后,可以獲得更多的信息。因為熱敏打印機只能打印黑白兩色,所以首先把圖片轉成純黑白的,再調用圖片打印指令進行打印。
3.1 打印圖片指令

這個指令的參數很多,一個一個來說:
3.2 圖片分辨率調整
如果分辨率過大,超過了打印機可打印的最大寬度,那么超出的部分將無法打印。我試驗的這臺最大寬度是 384 個像素點,超過這個寬度的數據無法被打印出來。所以在開始打印之前,我們需要調整圖片的分辨率。代碼如下:
/** * 對圖片進行壓縮(去除透明度) * * @param bitmapOrg */ public static Bitmap compressPic(Bitmap bitmap) { // 獲取這個圖片的寬和高 int width = bitmap.getWidth(); int height = bitmap.getHeight(); // 指定調整后的寬度和高度 int newWidth = 240; int newHeight = 240; Bitmap targetBmp = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); Canvas targetCanvas = new Canvas(targetBmp); targetCanvas.drawColor(0xffffffff); targetCanvas.drawBitmap(bitmap, new Rect(0, 0, width, height), new Rect(0, 0, newWidth, newHeight), null); return targetBmp; }3.2 圖片黑白化處理
因為能夠打印的圖像只有黑白兩色,所以需要先做黑白化的處理。這一部分其實又細分為彩色圖片->灰度圖片,灰度圖片->黑白圖片兩步。直接上代碼:
/** * 灰度圖片黑白化,黑色是1,白色是0 * * @param x 橫坐標 * @param y 縱坐標 * @param bit 位圖 * @return */ public static byte px2Byte(int x, int y, Bitmap bit) { if (x < bit.getWidth() && y < bit.getHeight()) { byte b; int pixel = bit.getPixel(x, y); int red = (pixel & 0x00ff0000) >> 16; // 取高兩位 int green = (pixel & 0x0000ff00) >> 8; // 取中兩位 int blue = pixel & 0x000000ff; // 取低兩位 int gray = RGB2Gray(red, green, blue); if (gray < 128) { b = 1; } else { b = 0; } return b; } return 0; } /** * 圖片灰度的轉化 */ private static int RGB2Gray(int r, int g, int b) { int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); //灰度轉化公式 return gray; }其中的灰度化轉換公式是一個廣為流傳的公式,具體原理不明。我們直接看灰度轉化為黑白的函數 px2Byte(int x, int y, Bitmap bit)。對于一個 Bitmap 中的任意一個坐標點,取出其 RGB 三色信息后做灰度化處理,然后對于灰度小于128的,用黑色表示,灰度大于128的,用白色表示。
3.3 逐行打印圖片
其實打印圖片和打印文本是一樣的,也是一行一行的打印。直接上代碼吧,注釋已經盡量詳細了。
/************************************************************************* * 假設一個240*240的圖片,分辨率設為24, 共分10行打印 * 每一行,是一個 240*24 的點陣, 每一列有24個點,存儲在3個byte里面。 * 每個byte存儲8個像素點信息。因為只有黑白兩色,所以對應為1的位是黑色,對應為0的位是白色 **************************************************************************/ /** * 把一張Bitmap圖片轉化為打印機可以打印的字節流 * * @param bmp * @return */ public static byte[] draw2PxPoint(Bitmap bmp) { //用來存儲轉換后的 bitmap 數據。為什么要再加1000,這是為了應對當圖片高度無法 //整除24時的情況。比如bitmap 分辨率為 240 * 250,占用 7500 byte, //但是實際上要存儲11行數據,每一行需要 24 * 240 / 8 =720byte 的空間。再加上一些指令存儲的開銷, //所以多申請 1000byte 的空間是穩妥的,不然運行時會拋出數組訪問越界的異常。 int size = bmp.getWidth() * bmp.getHeight() / 8 + 1000; byte[] data = new byte[size]; int k = 0; //設置行距為0的指令 data[k++] = 0x1B; data[k++] = 0x33; data[k++] = 0x00; // 逐行打印 for (int j = 0; j < bmp.getHeight() / 24f; j++) { //打印圖片的指令 data[k++] = 0x1B; data[k++] = 0x2A; data[k++] = 33; data[k++] = (byte) (bmp.getWidth() % 256); //nL data[k++] = (byte) (bmp.getWidth() / 256); //nH //對于每一行,逐列打印 for (int i = 0; i < bmp.getWidth(); i++) { //每一列24個像素點,分為3個字節存儲 for (int m = 0; m < 3; m++) { //每個字節表示8個像素點,0表示白色,1表示黑色 for (int n = 0; n < 8; n++) { byte b = px2Byte(i, j * 24 + m * 8 + n, bmp); data[k] += data[k] + b; } k++; } } data[k++] = 10;//換行 } return data; }4. 總結
用兩篇介紹了一個比較冷門的應用,純粹是因為自己花了很多時間去搞懂原理,所以希望記錄下來。尤其是圖片打印部分,廢了好多紙啊哈哈哈,一個字節操作錯誤,打印出來就是一堆亂碼。感覺和 java 的 .class 文件很像,每一個指令占用多少位,每一位表示什么都是嚴格規定好的,不能超出也不能缺少。
最后希望能幫到需要的人吧,感覺網上這部分資料還是比較少的。也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答