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

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

對應(yīng)的發(fā)送指令的代碼:
/* 設(shè)置文本對齊方式
* @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();
}
與初始化指令不同的是,這條指令帶有一個參數(shù)n。
換行和制表符:
直接輸出對應(yīng)的字符:
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();
}
這兩個指令在打印訂單詳情的時候使用最多。尤其是制表符,可以讓每一列的文字對齊。
設(shè)置行間距:

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

這個指令的參數(shù)很多,一個一個來說:
- m:取值十進制 0、1、32、33。設(shè)置打印精度,0、1對應(yīng)每行8個點,32、33對應(yīng)每行24個點,對應(yīng)最高的打印精度(其實這里也沒太搞清楚取值0、1或者取值32、33的區(qū)別,只要記住取值33,對應(yīng)每行24個點,后面還有用)
- n1, n2 : 表示圖片的寬度,為什么有兩個?其實只是分成了高位和低位兩部分,因為每部分只有8bit,最大表示256。所以 n1 = 圖片寬度 % 256,n2 = 圖片寬度 / 256。假設(shè)圖片寬300,那么n1=1,n2=44
- d1 d2 ... dk 這部分就是轉(zhuǎn)換成字節(jié)流的圖像數(shù)據(jù)了
3.2 圖片分辨率調(diào)整
如果分辨率過大,超過了打印機可打印的最大寬度,那么超出的部分將無法打印。我試驗的這臺最大寬度是 384 個像素點,超過這個寬度的數(shù)據(jù)無法被打印出來。所以在開始打印之前,我們需要調(diào)整圖片的分辨率。代碼如下:
/**
* 對圖片進行壓縮(去除透明度)
*
* @param bitmapOrg
*/
public static Bitmap compressPic(Bitmap bitmap) {
// 獲取這個圖片的寬和高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
// 指定調(diào)整后的寬度和高度
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;
}
/**
* 圖片灰度的轉(zhuǎn)化
*/
private static int RGB2Gray(int r, int g, int b) {
int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); //灰度轉(zhuǎn)化公式
return gray;
}
其中的灰度化轉(zhuǎn)換公式是一個廣為流傳的公式,具體原理不明。我們直接看灰度轉(zhuǎn)化為黑白的函數(shù) px2Byte(int x, int y, Bitmap bit)。對于一個 Bitmap 中的任意一個坐標點,取出其 RGB 三色信息后做灰度化處理,然后對于灰度小于128的,用黑色表示,灰度大于128的,用白色表示。
3.3 逐行打印圖片
其實打印圖片和打印文本是一樣的,也是一行一行的打印。直接上代碼吧,注釋已經(jīng)盡量詳細了。
/*************************************************************************
* 假設(shè)一個240*240的圖片,分辨率設(shè)為24, 共分10行打印
* 每一行,是一個 240*24 的點陣, 每一列有24個點,存儲在3個byte里面。
* 每個byte存儲8個像素點信息。因為只有黑白兩色,所以對應(yīng)為1的位是黑色,對應(yīng)為0的位是白色
**************************************************************************/
/**
* 把一張Bitmap圖片轉(zhuǎn)化為打印機可以打印的字節(jié)流
*
* @param bmp
* @return
*/
public static byte[] draw2PxPoint(Bitmap bmp) {
//用來存儲轉(zhuǎn)換后的 bitmap 數(shù)據(jù)。為什么要再加1000,這是為了應(yīng)對當圖片高度無法
//整除24時的情況。比如bitmap 分辨率為 240 * 250,占用 7500 byte,
//但是實際上要存儲11行數(shù)據(jù),每一行需要 24 * 240 / 8 =720byte 的空間。再加上一些指令存儲的開銷,
//所以多申請 1000byte 的空間是穩(wěn)妥的,不然運行時會拋出數(shù)組訪問越界的異常。
int size = bmp.getWidth() * bmp.getHeight() / 8 + 1000;
byte[] data = new byte[size];
int k = 0;
//設(shè)置行距為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個字節(jié)存儲
for (int m = 0; m < 3; m++) {
//每個字節(jié)表示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. 總結(jié)
用兩篇介紹了一個比較冷門的應(yīng)用,純粹是因為自己花了很多時間去搞懂原理,所以希望記錄下來。尤其是圖片打印部分,廢了好多紙啊哈哈哈,一個字節(jié)操作錯誤,打印出來就是一堆亂碼。感覺和 java 的 .class 文件很像,每一個指令占用多少位,每一位表示什么都是嚴格規(guī)定好的,不能超出也不能缺少。
最后希望能幫到需要的人吧,感覺網(wǎng)上這部分資料還是比較少的。也希望大家多多支持腳本之家。
- Android實現(xiàn)PDF預(yù)覽打印功能
- Android gradle插件打印時間戳的方法詳解
- Android編程實現(xiàn)計算兩個日期之間天數(shù)并打印所有日期的方法
- Android中如何安全地打印日志詳解
- Mac 下 Android Studio 不打印日志的解決辦法
- Android jni調(diào)試打印char陣列的實例詳解
- Android下的POS打印機調(diào)用的簡單實現(xiàn)
- Android 藍牙連接 ESC/POS 熱敏打印機打印實例(藍牙連接篇)
- Android打印機--小票打印格式及模板設(shè)置實例代碼
- Android進階——安卓調(diào)用ESC/POS打印機打印實例
- Android手機通過藍牙連接佳博打印機的實例代碼
- Android實現(xiàn)系統(tǒng)打印功能
相關(guān)文章
android開發(fā)教程之startActivityForResult使用方法
這篇文章主要介紹了android開發(fā)教程之startActivityForResult使用方法,需要的朋友可以參考下2014-03-03
基于RecyclerView實現(xiàn)橫向GridView效果
這篇文章主要為大家詳細介紹了基于RecyclerView實現(xiàn)橫向GridView效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07
Android中TextView實現(xiàn)超過固定行數(shù)顯示“...展開全部”
這篇文章主要給大家介紹了關(guān)于Android中TextView如何實現(xiàn)超過固定行數(shù)顯示"...展開全部"的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2017-12-12
深入理解Android熱修復(fù)技術(shù)原理之資源熱修復(fù)技術(shù)
Android資源的熱修復(fù),就是在app不重新安裝的情況下,利用下發(fā)的補丁包 直接更新本app中的資源2021-06-06
android自動生成dimens適配文件的圖文教程詳解(無需Java工具類)
這篇文章主要介紹了android自動生成dimens適配文件,無需Java工具類,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03
詳解基于Android的Appium+Python自動化腳本編寫
這篇文章主要介紹了詳解基于Android的Appium+Python自動化腳本編寫,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08
Android開發(fā)ThreadPoolExecutor與自定義線程池詳解
這篇文章主要為大家介紹了Android開發(fā)ThreadPoolExecutor與自定義線程池詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11

