一、概述
目前打印打印机支持的无线打印方式一般为wifi和蓝牙。
蓝牙打印关于蓝牙连接部分请查看上篇文章 -> Android 蓝牙连接 ,本篇文章讨论Android中蓝牙打印的指令实现。
蓝牙打印机的种类繁多,支持的打印格式也不尽相同。按照指令集划分,主要可分为:ESC指令集、CPCL指令集。
大部分热敏打印机使用的ESC指令集。部分支持CPCL指令集。
二、ESC指令
指令一览表:
常用打印指令:
1、初始化
2、设置文本对齐方式
3、设置行间距
1)默认行间距
2)指定行间距
4、设定字符打印模式
5、打印图片
参数说明:
m
:取值十进制 0、1、32、33。设置打印精度,0、1对应每行8个点,32、33对应每行24个点,对应最高的打印精度(其实这里也没太搞清楚取值0、1或者取值32、33的区别,只要记住取值33,对应每行24个点,后面还有用)n1, n2
: 表示图片的宽度,为什么有两个?其实只是分成了高位和低位两部分,因为每部分只有8bit,最大表示256。所以 n1 = 图片宽度 % 256,n2 = 图片宽度 / 256。假设图片宽300,那么n1=1,n2=44d1 d2 ... dk
这部分就是转换成字节流的图像数据了更多ESC指令详情跳转 -> ESC/POS 打印控制命令
需要注意的是:在实际打印中,图片的分辨率需要调整,宽度(每行像素点数)不能超出打印机可支持范围。所以图片需要做一下压缩处理,同时,由于打印图片是黑白的,所以需要灰度转化对图片进行黑白化处理。
此处贴出一个ESC打印工具类,未使用到的指令可以依照指令表实现。
public class PrinterManagerForESC extends PrinterManager{
private OutputStreamWriter writer = null;
private OutputStream outputStream = null;
public PrinterManagerForESC(OutputStream outputStream, String encoding) throws IOException {
writer = new OutputStreamWriter(outputStream, encoding);
this.outputStream = outputStream;
initPrinter();
}
//初始化
protected void initPrinter() throws IOException {
writer.write(0x1B);
writer.write(0x40);
completeFlush();
}
public void completeFlush() throws IOException {
writer.flush();
}
public void printString(String str){
try {
writer.write(str);
completeFlush();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
void printStrWithAlign(String value, int alignment) {
try {
writer.write(0x1b);
writer.write(0x61);
writer.write(alignment);
writer.write(value);
writer.write("\n");
completeFlush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 打印分割线
*/
public void printSeparateLine(int paperpoints){
String res = "";
int count = Math.round(paperpoints/12);
for (int i=0; i< count; i++){
res = res + "-";
if (i== (count-1)){
res = res + "\n";
}
}
printString(res);
}
private void printBytes(byte[] bytes){
try {
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void printBitmap(Bitmap bitmap, int paperwidth) throws IOException {
Bitmap resizeBitmap = compressPic(bitmap,paperwidth);
byte[] bmpByteArray = draw2PxPoint(resizeBitmap);
printBytes(bmpByteArray);
}
/**
* 打印换行
*
* @return length 需要打印的换行数
* @throws IOException
*/
public void printNextLine(int lineNum) {
try {
for (int i = 0; i < lineNum; i++) {
writer.write("\n");
}
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/*************************************************************************
* 假设一个360*360的图片,分辨率设为24, 共分15行打印 每一行,是一个 360 * 24 的点阵,y轴有24个点,存储在3个byte里面。
* 即每个byte存储8个像素点信息。因为只有黑白两色,所以对应为1的位是黑色,对应为0的位是白色
**************************************************************************/
private byte[] draw2PxPoint(Bitmap bmp) {
//计算所需字节空间大小
int bitmapsize = (int) (Math.ceil(bmp.getHeight() / 24f)) * bmp.getWidth() * 3;
int cmdsize = (int) (Math.ceil(bmp.getHeight() / 24f)) * 6 + 5;
int size = bitmapsize + cmdsize;
byte[] tmp = new byte[size];
int k = 0;
// 设置行距为0
tmp[k++] = 0x1B;
tmp[k++] = 0x33;
tmp[k++] = 0x00;
for (int j = 0; j < bmp.getHeight() / 24f; j++) {
tmp[k++] = 0x1B;
tmp[k++] = 0x2A;// 0x1B 2A 表示图片打印指令
tmp[k++] = 33; // m=33时,选择24点密度打印
tmp[k++] = (byte) (bmp.getWidth() % 256); // nL
tmp[k++] = (byte) (bmp.getWidth() / 256); // nH
for (int i = 0; i < bmp.getWidth(); i++) {
for (int m = 0; m < 3; m++) {
for (int n = 0; n < 8; n++) {
byte b = px2Byte(i, j * 24 + m * 8 + n, bmp);
tmp[k] += tmp[k] + b;
}
k++;
}
}
tmp[k++] = 10;// 换行
}
// 恢复默认行距
tmp[k++] = 0x1B;
tmp[k++] = 0x32;
byte[] result = new byte[k];
System.arraycopy(tmp, 0, result, 0, k);
return result;
}
/**
* 图片二值化,黑色是1,白色是0
*
* @param x 横坐标
* @param y 纵坐标
* @param bit 位图
* @return
*/
private 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 int RGB2Gray(int r, int g, int b) {
int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); // 灰度转化公式
return gray;
}
public byte[] getGbkStr(String stText) {
byte[] returnText = null;
try {
returnText = stText.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return returnText;
}
private int getStringPixLength(String str) {
int pixLength = 0;
char c;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (Pinyin.isChinese(c)) {
pixLength += 24;
} else {
pixLength += 12;
}
}
return pixLength;
}
/**
* 图片压缩
*/
public static Bitmap compressPic(Bitmap bitmap, int paperwidth) {
// 获取这个图片的宽和高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int newWidth = paperwidth;
int newHeight = Math.round ( height * newWidth / width);
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;
}
}
三、CPCL指令
常用指令:
1、打印机起始命令
2、打印命令
3、结束命令
4、文本命令
5、图片命令
更多CPCL指令文档跳转 -> CPCL指令集
此处贴出一个CPCL指令工具类,未实现的指令可按照文档实现。
public class PrinterManagerForCPCL extends PrinterManager{
private OutputStreamWriter writer = null;
private OutputStream outputStream = null;
private String dpi;
private StringBuffer buffer;
public PrinterManagerForCPCL(OutputStream outputStream, String encoding, String dpi) throws IOException {
writer = new OutputStreamWriter(outputStream,encoding);
this.outputStream = outputStream;
this.dpi = dpi;
this.buffer = new StringBuffer("");
}
//开始指令
public void beginInstruction(int height) throws IOException {
// {offset} {hegiht} {qty}
buffer = new StringBuffer("");
String begincmd = "! 0 " + dpi + " " + dpi + " " + height + " " + 1 + "\r\n";
buffer.append(begincmd);
}
//结束指令
public void endInstructionFlush() throws IOException {
buffer.append("FORM\r\n");
buffer.append("PRINT\r\n");
byte[] res = buffer.toString().getBytes();
outputStream.write(res);
outputStream.flush();
}
public void endPrintForStr() throws IOException {
buffer.append("PRINT\r\n");
byte[] res = buffer.toString().getBytes("GBK");
outputStream.write(res);
outputStream.flush();
}
public void printString(String str){
try {
writer.write(str);
completeFlush();
} catch (IOException e) {
e.printStackTrace();
}
}
public void completeFlush() throws IOException {
writer.flush();
}
/**
* 打印CPCL字符串
*/
public void printCPCLString(String data) throws IOException {
//TEXT(或T) {font} {size} {x} {y} {data}
String strCmd = "TEXT " + 1 + " " + 0 + " " + 0 + " " + 0 + " " + data + "\r\n";
buffer.append(strCmd);
}
/**
* 打印图片
*
* @param bitmap
* @throws IOException
*/
public void printImage(Bitmap bitmap){
int loopWidth = 8 - (bitmap.getWidth() % 8);
if (loopWidth == 8) {
loopWidth = bitmap.getWidth();
} else {
loopWidth += bitmap.getWidth();
}
buffer.append("EG "+ (loopWidth / 8) + " "+ bitmap.getHeight() + " 0 0 "+"");
for (int y = 0; y < bitmap.getHeight(); y++) {
int bit = 128;
int currentValue = 0;
for (int x = 0; x < loopWidth; x++) {
int intensity;
if (x < bitmap.getWidth()) {
int color = bitmap.getPixel(x, y);
intensity = 255 - ((Color.red(color) + Color.green(color) + Color.blue(color)) / 3);
} else {
intensity = 0;
}
if (intensity >= 128) currentValue |= bit;
bit = bit >> 1;
if (bit == 0) {
String hex = IntegerToHexString(currentValue);
buffer.append(hex.toUpperCase() + " ");
bit = 128;
currentValue = 0;
}
}
}
buffer.append("\r\n");
}
private String IntegerToHexString(int data) {
String s = Integer.toHexString(data);
return s.length() < 2 ? s = "0" + s : s;
}
public void printSeparateLine(int paperpoints){
String res = "";
int count = Math.round(paperpoints / 12);
for (int i = 0; i < count; i++) {
res = res + "-";
if (i == (count - 1)) {
res = res + "\n";
}
}
printString(res);
}
@Override
void printStrWithAlign(String value, int alignment) {
try {
//居中特殊处理
if (1 == alignment){
beginInstruction(24);
buffer.append("CENTER\r\n");
printCPCLString(value);
endPrintForStr();
}else {
writer.write(value);
writer.write("\n");
completeFlush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void printBytes(byte[] bytes) {
try {
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 打印换行
*
* @return length 需要打印的换行数
* @throws IOException
*/
public void printNextLine(int lineNum){
try {
for (int i = 0; i < lineNum; i++) {
writer.write("\r\n");
}
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
public byte[] getGbkStr(String stText) {
byte[] returnText = null;
try {
returnText = stText.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return returnText;
}
private int getStringPixLength(String str) {
int pixLength = 0;
char c;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (Pinyin.isChinese(c)) {
pixLength += 24;
} else {
pixLength += 12;
}
}
return pixLength;
}
/**
* 图片压缩
* @param bitmap
*/
public static Bitmap compressPic(Bitmap bitmap, int paperwidth) {
// 获取这个图片的宽和高
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int newWidth = paperwidth;
int newHeight = Math.round ( height * newWidth / width);
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;
}
}