微信小程序连接蓝牙打印机打印二维码和内容
日期:2019-12-16
来源:程序思维浏览:5444次
目前小程序生态越来越丰富,微信给予了小程序一定的硬件通信能力这是之前 Web 很少尝试的事情。关于蓝牙,常见的就下面几个 API:
startBluetoothDevicesDiscovery 开始搜寻附近的蓝牙外围设备
openBluetoothAdapter 初始化蓝牙模块
wx.onBluetoothDeviceFound 监听寻找到新设备的事件
wx.writeBLECharacteristicValue 向低功耗蓝牙设备特征值中写入二进制数据
wx.onBLEConnectionStateChange 监听低功耗蓝牙连接状态的改变事件
wx.createBLEConnection 连接低功耗蓝牙设备
不过实际我们在实现过程中,我们可能还会遇到一些本身 API 在不同平台上 BUG。
中文乱码
打印的时候,第一个发现的问题便是打印机无法正常打印中文字符串。在向蓝牙写数据的时候,我们实际上市向蓝牙发送的 buffer ,因此我们需要将对应的字符串转换成设备可支持的中文转码,比如 GBK GB2312,网上有一些现成的库,https://github.com/inexorabletash/text-encoding
这样的话,我们只需要进行引用,然后
// text-encoding 为引用的代码目录
import { TextEncoder } from '../text-encoding';
this._encoder = new TextEncoder("gb2312", {NONSTANDARD_allowLegacyEncoding: true});
// content 需要打印的字符串
const uint8Array = this._encoder.encode(content);
由于小程序包大小的限制,可以手动将 encoding-indexs.js 转换成 JSON 放到远程,然后动态 request 下来。
设置格式
我们在打印的时候,往往不是简单的一串串字符串,而是需要进行大小,对齐方式的跳转。打印机是能够接受 ESC/POS
其中有一些我么需要用到的指令。
ASCII码 ESC a n
十进制码 27 97 n
其中 n 的值表示不同的对齐方式:
0 左对齐
1 中间对齐
2 右对齐
我们则在发给打印机的 buffer 数据里需要包含对应的命令
[[27, 97,1], [....]]
打印接下来按照中间对齐进行打印。
当然除了对齐,我们还能对字体大小和粗细进行调整。
字体加粗
ASCII码 ESC ! n 十进制码 27 33 n
0 取消加粗
8 加粗
字体大小
ASCII码 ESC ! n 十进制码 27 33 n
0 正常
16 倍高
32 倍宽
当然,我们其实不用太详细的去了解这些指令,推荐一个打印库,它类似翻译了这些指令,可以按照前端的方式进行打印。https://github.com/benioZhang/miniprogram-bluetoothprinter/tree/master/printer
const printerJobs = new PrinterJobs();
printerJobs.print('')
.setAlign('ct') // 设置居中对齐
.setSize(2, 2) // 设置字体大小
.print(name)
await printerJobs.printQRCode(qrcode); //qrcode是二维码图片路径
printerJobs.setSize(1, 1)
.print(printerUtil.inline('地址:', address)) // 两边对齐
.print('')
.print(printerUtil.fillLine()) // 打印虚线
.println();
const buffer = printerJobs.buffer();
打印二维码
当然现在小票上都有二维码,因此在打印二维码的时候,我们需要关注大小和 canvas 的换算。
其实打印二维码的流程主要是下面这几部
getImageInfo(微信获取二维码图像信息) -> createCanvasContext(微信创建 Canvas 上下文) -> drawImage (在 canvas 上绘制二维码) -> canvasGetImageData (获取 canvas 上的像素数据) -> 灰度运算 / 像素转换成点 -> toBuffer(转换成 Buffer 数据发送给蓝牙)
我们在上面 printerjobs.js 扩展就是类似下面
const _drawQRCode = async (path, width, height, callback) => {
// 'canvas' mean the canvas id in your view
const ctx = wx.createCanvasContext('canvas');
// const ctx = offscreenCanvas.getContext('2d');
ctx.drawImage(path, 0, 0, width, height, 0, 0, 256, 256);
ctx.draw(false, () => {
wx.canvasGetImageData({
canvasId: 'canvas',
x: 0,
y: 0,
width: 256,
height: 256,
success: (res) => {
let arr = util.convert4to1(res.data);
let data = util.convert8to1(arr);
const cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 32, 0, 0, 1], data, [27, 74, 3], [27, 64]);
const buffer = Buffer.from(cmds, 'gb2312');
callback(buffer);
},
fail: function(error) {
console.log("error:", error);
},
complete: () => {
// console.log('finished');
}
})
});
}
printerJobs.prototype.printQRCode = async function (qrcode) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: qrcode,
success: (result) => {
setTimeout(() => {
const path = result.path;
_drawQRCode(path, result.width, result.height, (cmds) => {
// const uint8Array = new Uint8Array(cmds);
this._enqueue(cmds);
this._enqueue(commands.LF);
resolve();
});
}, 200);
},
fail: () => {
reject('cannot draw image');
}
})
})
};
其中大家需要理解
[27, 97, 1], [29, 118, 48, 0, 32, 0, 0, 1], data, [27, 74, 3], [27, 64]
这其中 [27, 97, 1] 我们上面已经知道,这其实表示对齐方式,为居中对齐。
[29, 118, 48, 0, 32, 0, 0, 1] 则是表示打印光栅位图。
ASCII码 GS v 0 m xL xH yL yH d1...dk
十进制码 29 118 48 m xL xH yL yH d1...dk
其中 m 表示设置是否放大打印图像
常用的打印命令介绍
初始化打印机
指令:
ASCII码 ESC @
十进制码 27 64
说明
这个指令会清楚打印缓冲区中的数据,但是接收缓冲区的数据并不会清除,一般开始打印的时候需要调用
代码
byte [] esc_init=new byte[]{27,64};
mDeviceConnection.bulkTransfer(endpointOut, esc_init, esc_init.length, TIME_OUT);
设置对齐方式
指令:
ASCII码 ESC a n
十进制码 27 97 n
参数含义:
n 对齐方式
0,48 左对齐
1,49 中间对齐
2,50 右对齐
代码
byte [] esc_gravity=new byte[] {27,97,0}//左对齐
mDeviceConnection.bulkTransfer(endpointOut, esc_gravity, esc_gravity.length, TIME_OUT);
字体加粗
指令:
ASCII码 ESC ! n
十进制码 27 33 n
参数含义:
n 效果
0 取消加粗
8 加粗
代码
byte [] esc_bold=new byte[] {27,33,8}//加粗
mDeviceConnection.bulkTransfer(endpointOut, esc_bold, esc_bold.length, TIME_OUT);
字体倍高倍宽
指令:
ASCII码 ESC ! n
十进制码 27 33 n
参数含义:
n 效果
0 正常
16 倍高
32 倍宽
走纸
指令:
ASCII码 ESC d n
十进制码 27 100 n
参数含义:
0<=n<=255
说明
打印缓冲区中的数据并向前走纸n行
打印光栅位图
指令:
ASCII码 GS v 0 m xL xH yL yH d1...dk
十进制码 29 118 48 m xL xH yL yH d1...dk
参数含义
m :指的是打印模式
m值 打印模式
0,48 正常
1,49 倍宽
2,50 倍高
3,51 倍宽倍高
xL:位图宽度以双字节表示的低位数值
XH:位图宽度以双字节表示的高位数值
比如位图宽度是200,宽度占的字节数=200/8=25;
为什么除以8?因为待打印位图的位图是灰度图,一个像素占用一个字节。后面解释什么是灰度图。
25的二进制表示是00000000 00011001。所以xL=0(高8位),xH=25(低8位);
所以水平方向位图点数为 (xL+xH256)8
yL:位图高度以双字节表示的低位数值
yH:位图高度以双字节表示的高位数值
竖直方向位图点数为 yL+yH*256
比如位图高度是200
200的二进制表示是 00000000 11001000。所以yL=200,yH=0;
d1...dk:代表位图数据,每个字节相应位为1表示打印该点,为0不打印该点。
举例:
假设图片尺寸200px200px
那么(xL+xH256)=25
yL+yH*256=200;
1 2 3 ... 23 24 25
26 27 28 ... 48 49 50
... ... ... ... ... ... ...
4976 4977 4978 ... 4998 4999 5000
其中每个小格由横向的8位组成,相应位为1表示打印该点,为0不打印该点。
规范化位图
用户传入的位图的尺寸是随意的,需要将位图的宽度规范化为8的整数倍。
private Bitmap resizeImage(Bitmap bitmap, int requestWidth) {
//将位图宽度规范化为8的整数倍
int legalWidth=(requestWidth+7)/8*8;
int height= (int) (legalWidth*bitmap.getHeight()/(float)bitmap.getWidth());
return Bitmap.createScaledBitmap(bitmap,legalWidth,height,true);
}
图像的灰度化
首先什么是灰度化?
在RGB模型中,当R=G=B时,则彩色表示一种灰度颜色,其中R=G=B的值叫灰度值。因此,灰度图每个像素只需一个字节存放灰度值,灰度范围为0-255.一般有分量法 、最大值法、平均值法、加权平均法对彩色图像进行灰度化。
说一下加权平均法:
由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到比较合理的灰度图像。
Gray=0.30R+0.59G+0.11B
程序实现
private int grayPixle(int pixel) {
int red=(pixel & 0x00ff0000) >> 16;//获取r分量
int green= (pixel & 0x0000ff00) >> 8;//获取g分量
int blue= pixel & 0x000000ff;//获取b分量
return (int) (red*0.3f+green*0.59f+blue*0.11f);//加权平均法进行灰度化
}
灰度图的二值化
图像的二值化就是将图像上的像素点的灰度值设置为0或或者255,也就是将整个图像呈现出明显的黑白效果的过程。所有灰度大于或等于阈值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。
阈值的选取是至关重要,直接影响着二值化后的图片的质量。对于我们的打印程序选128就行,要求不高。
//存储位图数据d1...dk
byte[] data = new byte[width * height];
int index = 0;
int temp = 0;
int part[]=new int[8];
//for循环顺序不要错了,外层遍历高度,内层遍历宽度,因为横向每8个像素点组成一个字节。
for (int j=0;j<bitmap.getHeight();j++){
for (int i=0;i<bitmap.getWidth();i+=8){
//横向每8个像素点组成一个字节。
for(int k=0;k<8;k++) {
int pixel = bitmap.getPixel(i+k, j);
int grayPixle = grayPixle(pixel);
if (grayPixle >128) {
//灰度值大于128位 白色 为第k位0不打印
part[k]=0;
} else {
part[k]=1;
}
}
//128千万不要写成2^7,^是异或操作符
temp=part[0]*128+
part[1]*64+
part[2]*32+
part[3]*16+
part[4]*8+
part[5]*4+
part[6]*2+
part[7]*1;
data[index++] = (byte) temp;
}
}
以上就获得了d1...dk位图数据
完整的代码
// 使用光栅位图的打印方式
public void printBitmap(Bitmap bitmap,int requestWidth) throws Exception {
// GS v 0 m xL xH yL yH d1...dk
if (bitmap == null) {
throw new Exception("bitmap is null");
}
//规范化位图宽高
bitmap=resizeImage(bitmap,requestWidth);
int width = bitmap.getWidth() / 8;
int height = bitmap.getHeight();
byte []cmd= new byte[width * height+4+4];
cmd[0]=29;
cmd[1]=118;
cmd[2]=48;
cmd[3]=0;
cmd[4]= (byte) (width % 256);//计算xL
cmd[5]=(byte) (width / 256);//计算xH
cmd[6]= (byte) (height % 256);//计算yL
cmd[7]=(byte) (height / 256);//计算yH
int index = 8;
int temp = 0;
int part[]=new int[8];
for (int j=0;j<bitmap.getHeight();j++){
for (int i=0;i<bitmap.getWidth();i+=8){
//横向每8个像素点组成一个字节。
for(int k=0;k<8;k++) {
int pixel = bitmap.getPixel(i+k, j);
int grayPixle = grayPixle(pixel);
if (grayPixle >128) {
//灰度值大于128位 白色 为第k位0不打印
part[k]=0;
} else {
part[k]=1;
}
}
//128千万不要写成2^7,^是异或操作符
temp=part[0]*128+
part[1]*64+
part[2]*32+
part[3]*16+
part[4]*8+
part[5]*4+
part[6]*2+
part[7]*1;
cmd[index++] = (byte) temp;
}
}
mDeviceConnection.bulkTransfer(endpointOut, cmd, esc_bold.length, TIME_OUT);
}
xl XH yL yH 需要我们自己进行计算
比如你的canvas打印的大小为 120 x 120
xl = 120 / 8 % 256
xH = 120 / 8 / 256 // 取整
yl = 120 % 256;
yH = 120 / 256 // 取整
所以你的指令就是 [29, 118, 48, 0, 15, 120, 0, 1]。
如果这个错误,一般打印出来也是乱码。
convert4to1 和 convert8to1 比较常规的实现:
function convert4to1(res) {
let arr=[];
for (let i = 0; i < res.length; i++) {
if (i % 4 == 0) {
let rule = 0.29900 * res[i] + 0.58700 * res[i + 1] + 0.11400 * res[i + 2];
if (rule > 200) {
res[i] = 0;
} else {
res[i] = 1;
}
arr.push(res[i]);
}
}
return arr;
}
function convert8to1(arr) {
let data = [];
for (let k = 0; k < arr.length; k += 8) {
let temp = arr[k] * 128 + arr[k + 1] * 64 + arr[k + 2] * 32 + arr[k + 3] * 16 + arr[k + 4] * 8 + arr[k + 5] * 4 + arr[k + 6] * 2 + arr[k + 7] * 1
data.push(temp);
}
return data;
}
Android 蓝牙连接找不到设备
经常在测试的时候,发现第一次扫码连接正常,而第二次就死活都不进行设备扫描了。主要就是不再触发 onBluetoothDeviceFound 。原因是 Android 我们之前已经建立连接了,但是我们经常重新扫描的时候,并没有断开。因此建议我们手动关闭连接然后再重新打开连接。
wx.closeBluetoothAdapter();
wx.openBluetoothAdapter();
处理蓝牙断开
这是两方面的问题,一个蓝牙未打开,而是设备工作中突然断掉(停电或者手误关闭等)
微信有两个 API 可以帮助你解决这个问题:
// 处理手机蓝牙打开和关闭
wx.onBluetoothAdapterStateChange(function (res) {
console.log('onBluetoothAdapterStateChange', res);
if (res.available) {
// @TODO
}
});
// 处理设备突然断掉
wx.onBLEConnectionStateChange((res) => {
if (!res.connected && this.data.connected) {
@TODO
}
});
微信小程序连接蓝牙打印机打印二维码和内容源码下载
startBluetoothDevicesDiscovery 开始搜寻附近的蓝牙外围设备
openBluetoothAdapter 初始化蓝牙模块
wx.onBluetoothDeviceFound 监听寻找到新设备的事件
wx.writeBLECharacteristicValue 向低功耗蓝牙设备特征值中写入二进制数据
wx.onBLEConnectionStateChange 监听低功耗蓝牙连接状态的改变事件
wx.createBLEConnection 连接低功耗蓝牙设备
不过实际我们在实现过程中,我们可能还会遇到一些本身 API 在不同平台上 BUG。
中文乱码
打印的时候,第一个发现的问题便是打印机无法正常打印中文字符串。在向蓝牙写数据的时候,我们实际上市向蓝牙发送的 buffer ,因此我们需要将对应的字符串转换成设备可支持的中文转码,比如 GBK GB2312,网上有一些现成的库,https://github.com/inexorabletash/text-encoding
这样的话,我们只需要进行引用,然后
// text-encoding 为引用的代码目录
import { TextEncoder } from '../text-encoding';
this._encoder = new TextEncoder("gb2312", {NONSTANDARD_allowLegacyEncoding: true});
// content 需要打印的字符串
const uint8Array = this._encoder.encode(content);
由于小程序包大小的限制,可以手动将 encoding-indexs.js 转换成 JSON 放到远程,然后动态 request 下来。
设置格式
我们在打印的时候,往往不是简单的一串串字符串,而是需要进行大小,对齐方式的跳转。打印机是能够接受 ESC/POS
其中有一些我么需要用到的指令。
ASCII码 ESC a n
十进制码 27 97 n
其中 n 的值表示不同的对齐方式:
0 左对齐
1 中间对齐
2 右对齐
我们则在发给打印机的 buffer 数据里需要包含对应的命令
[[27, 97,1], [....]]
打印接下来按照中间对齐进行打印。
当然除了对齐,我们还能对字体大小和粗细进行调整。
字体加粗
ASCII码 ESC ! n 十进制码 27 33 n
0 取消加粗
8 加粗
字体大小
ASCII码 ESC ! n 十进制码 27 33 n
0 正常
16 倍高
32 倍宽
当然,我们其实不用太详细的去了解这些指令,推荐一个打印库,它类似翻译了这些指令,可以按照前端的方式进行打印。https://github.com/benioZhang/miniprogram-bluetoothprinter/tree/master/printer
const printerJobs = new PrinterJobs();
printerJobs.print('')
.setAlign('ct') // 设置居中对齐
.setSize(2, 2) // 设置字体大小
.print(name)
await printerJobs.printQRCode(qrcode); //qrcode是二维码图片路径
printerJobs.setSize(1, 1)
.print(printerUtil.inline('地址:', address)) // 两边对齐
.print('')
.print(printerUtil.fillLine()) // 打印虚线
.println();
const buffer = printerJobs.buffer();
打印二维码
当然现在小票上都有二维码,因此在打印二维码的时候,我们需要关注大小和 canvas 的换算。
其实打印二维码的流程主要是下面这几部
getImageInfo(微信获取二维码图像信息) -> createCanvasContext(微信创建 Canvas 上下文) -> drawImage (在 canvas 上绘制二维码) -> canvasGetImageData (获取 canvas 上的像素数据) -> 灰度运算 / 像素转换成点 -> toBuffer(转换成 Buffer 数据发送给蓝牙)
我们在上面 printerjobs.js 扩展就是类似下面
const _drawQRCode = async (path, width, height, callback) => {
// 'canvas' mean the canvas id in your view
const ctx = wx.createCanvasContext('canvas');
// const ctx = offscreenCanvas.getContext('2d');
ctx.drawImage(path, 0, 0, width, height, 0, 0, 256, 256);
ctx.draw(false, () => {
wx.canvasGetImageData({
canvasId: 'canvas',
x: 0,
y: 0,
width: 256,
height: 256,
success: (res) => {
let arr = util.convert4to1(res.data);
let data = util.convert8to1(arr);
const cmds = [].concat([27, 97, 1], [29, 118, 48, 0, 32, 0, 0, 1], data, [27, 74, 3], [27, 64]);
const buffer = Buffer.from(cmds, 'gb2312');
callback(buffer);
},
fail: function(error) {
console.log("error:", error);
},
complete: () => {
// console.log('finished');
}
})
});
}
printerJobs.prototype.printQRCode = async function (qrcode) {
return new Promise((resolve, reject) => {
wx.getImageInfo({
src: qrcode,
success: (result) => {
setTimeout(() => {
const path = result.path;
_drawQRCode(path, result.width, result.height, (cmds) => {
// const uint8Array = new Uint8Array(cmds);
this._enqueue(cmds);
this._enqueue(commands.LF);
resolve();
});
}, 200);
},
fail: () => {
reject('cannot draw image');
}
})
})
};
其中大家需要理解
[27, 97, 1], [29, 118, 48, 0, 32, 0, 0, 1], data, [27, 74, 3], [27, 64]
这其中 [27, 97, 1] 我们上面已经知道,这其实表示对齐方式,为居中对齐。
[29, 118, 48, 0, 32, 0, 0, 1] 则是表示打印光栅位图。
ASCII码 GS v 0 m xL xH yL yH d1...dk
十进制码 29 118 48 m xL xH yL yH d1...dk
其中 m 表示设置是否放大打印图像
常用的打印命令介绍
初始化打印机
指令:
ASCII码 ESC @
十进制码 27 64
说明
这个指令会清楚打印缓冲区中的数据,但是接收缓冲区的数据并不会清除,一般开始打印的时候需要调用
代码
byte [] esc_init=new byte[]{27,64};
mDeviceConnection.bulkTransfer(endpointOut, esc_init, esc_init.length, TIME_OUT);
设置对齐方式
指令:
ASCII码 ESC a n
十进制码 27 97 n
参数含义:
n 对齐方式
0,48 左对齐
1,49 中间对齐
2,50 右对齐
代码
byte [] esc_gravity=new byte[] {27,97,0}//左对齐
mDeviceConnection.bulkTransfer(endpointOut, esc_gravity, esc_gravity.length, TIME_OUT);
字体加粗
指令:
ASCII码 ESC ! n
十进制码 27 33 n
参数含义:
n 效果
0 取消加粗
8 加粗
代码
byte [] esc_bold=new byte[] {27,33,8}//加粗
mDeviceConnection.bulkTransfer(endpointOut, esc_bold, esc_bold.length, TIME_OUT);
字体倍高倍宽
指令:
ASCII码 ESC ! n
十进制码 27 33 n
参数含义:
n 效果
0 正常
16 倍高
32 倍宽
走纸
指令:
ASCII码 ESC d n
十进制码 27 100 n
参数含义:
0<=n<=255
说明
打印缓冲区中的数据并向前走纸n行
打印光栅位图
指令:
ASCII码 GS v 0 m xL xH yL yH d1...dk
十进制码 29 118 48 m xL xH yL yH d1...dk
参数含义
m :指的是打印模式
m值 打印模式
0,48 正常
1,49 倍宽
2,50 倍高
3,51 倍宽倍高
xL:位图宽度以双字节表示的低位数值
XH:位图宽度以双字节表示的高位数值
比如位图宽度是200,宽度占的字节数=200/8=25;
为什么除以8?因为待打印位图的位图是灰度图,一个像素占用一个字节。后面解释什么是灰度图。
25的二进制表示是00000000 00011001。所以xL=0(高8位),xH=25(低8位);
所以水平方向位图点数为 (xL+xH256)8
yL:位图高度以双字节表示的低位数值
yH:位图高度以双字节表示的高位数值
竖直方向位图点数为 yL+yH*256
比如位图高度是200
200的二进制表示是 00000000 11001000。所以yL=200,yH=0;
d1...dk:代表位图数据,每个字节相应位为1表示打印该点,为0不打印该点。
举例:
假设图片尺寸200px200px
那么(xL+xH256)=25
yL+yH*256=200;
1 2 3 ... 23 24 25
26 27 28 ... 48 49 50
... ... ... ... ... ... ...
4976 4977 4978 ... 4998 4999 5000
其中每个小格由横向的8位组成,相应位为1表示打印该点,为0不打印该点。
规范化位图
用户传入的位图的尺寸是随意的,需要将位图的宽度规范化为8的整数倍。
private Bitmap resizeImage(Bitmap bitmap, int requestWidth) {
//将位图宽度规范化为8的整数倍
int legalWidth=(requestWidth+7)/8*8;
int height= (int) (legalWidth*bitmap.getHeight()/(float)bitmap.getWidth());
return Bitmap.createScaledBitmap(bitmap,legalWidth,height,true);
}
图像的灰度化
首先什么是灰度化?
在RGB模型中,当R=G=B时,则彩色表示一种灰度颜色,其中R=G=B的值叫灰度值。因此,灰度图每个像素只需一个字节存放灰度值,灰度范围为0-255.一般有分量法 、最大值法、平均值法、加权平均法对彩色图像进行灰度化。
说一下加权平均法:
由于人眼对绿色的敏感最高,对蓝色敏感最低,因此,按下式对RGB三分量进行加权平均能得到比较合理的灰度图像。
Gray=0.30R+0.59G+0.11B
程序实现
private int grayPixle(int pixel) {
int red=(pixel & 0x00ff0000) >> 16;//获取r分量
int green= (pixel & 0x0000ff00) >> 8;//获取g分量
int blue= pixel & 0x000000ff;//获取b分量
return (int) (red*0.3f+green*0.59f+blue*0.11f);//加权平均法进行灰度化
}
灰度图的二值化
图像的二值化就是将图像上的像素点的灰度值设置为0或或者255,也就是将整个图像呈现出明显的黑白效果的过程。所有灰度大于或等于阈值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。
阈值的选取是至关重要,直接影响着二值化后的图片的质量。对于我们的打印程序选128就行,要求不高。
//存储位图数据d1...dk
byte[] data = new byte[width * height];
int index = 0;
int temp = 0;
int part[]=new int[8];
//for循环顺序不要错了,外层遍历高度,内层遍历宽度,因为横向每8个像素点组成一个字节。
for (int j=0;j<bitmap.getHeight();j++){
for (int i=0;i<bitmap.getWidth();i+=8){
//横向每8个像素点组成一个字节。
for(int k=0;k<8;k++) {
int pixel = bitmap.getPixel(i+k, j);
int grayPixle = grayPixle(pixel);
if (grayPixle >128) {
//灰度值大于128位 白色 为第k位0不打印
part[k]=0;
} else {
part[k]=1;
}
}
//128千万不要写成2^7,^是异或操作符
temp=part[0]*128+
part[1]*64+
part[2]*32+
part[3]*16+
part[4]*8+
part[5]*4+
part[6]*2+
part[7]*1;
data[index++] = (byte) temp;
}
}
以上就获得了d1...dk位图数据
完整的代码
// 使用光栅位图的打印方式
public void printBitmap(Bitmap bitmap,int requestWidth) throws Exception {
// GS v 0 m xL xH yL yH d1...dk
if (bitmap == null) {
throw new Exception("bitmap is null");
}
//规范化位图宽高
bitmap=resizeImage(bitmap,requestWidth);
int width = bitmap.getWidth() / 8;
int height = bitmap.getHeight();
byte []cmd= new byte[width * height+4+4];
cmd[0]=29;
cmd[1]=118;
cmd[2]=48;
cmd[3]=0;
cmd[4]= (byte) (width % 256);//计算xL
cmd[5]=(byte) (width / 256);//计算xH
cmd[6]= (byte) (height % 256);//计算yL
cmd[7]=(byte) (height / 256);//计算yH
int index = 8;
int temp = 0;
int part[]=new int[8];
for (int j=0;j<bitmap.getHeight();j++){
for (int i=0;i<bitmap.getWidth();i+=8){
//横向每8个像素点组成一个字节。
for(int k=0;k<8;k++) {
int pixel = bitmap.getPixel(i+k, j);
int grayPixle = grayPixle(pixel);
if (grayPixle >128) {
//灰度值大于128位 白色 为第k位0不打印
part[k]=0;
} else {
part[k]=1;
}
}
//128千万不要写成2^7,^是异或操作符
temp=part[0]*128+
part[1]*64+
part[2]*32+
part[3]*16+
part[4]*8+
part[5]*4+
part[6]*2+
part[7]*1;
cmd[index++] = (byte) temp;
}
}
mDeviceConnection.bulkTransfer(endpointOut, cmd, esc_bold.length, TIME_OUT);
}
xl XH yL yH 需要我们自己进行计算
比如你的canvas打印的大小为 120 x 120
xl = 120 / 8 % 256
xH = 120 / 8 / 256 // 取整
yl = 120 % 256;
yH = 120 / 256 // 取整
所以你的指令就是 [29, 118, 48, 0, 15, 120, 0, 1]。
如果这个错误,一般打印出来也是乱码。
convert4to1 和 convert8to1 比较常规的实现:
function convert4to1(res) {
let arr=[];
for (let i = 0; i < res.length; i++) {
if (i % 4 == 0) {
let rule = 0.29900 * res[i] + 0.58700 * res[i + 1] + 0.11400 * res[i + 2];
if (rule > 200) {
res[i] = 0;
} else {
res[i] = 1;
}
arr.push(res[i]);
}
}
return arr;
}
function convert8to1(arr) {
let data = [];
for (let k = 0; k < arr.length; k += 8) {
let temp = arr[k] * 128 + arr[k + 1] * 64 + arr[k + 2] * 32 + arr[k + 3] * 16 + arr[k + 4] * 8 + arr[k + 5] * 4 + arr[k + 6] * 2 + arr[k + 7] * 1
data.push(temp);
}
return data;
}
Android 蓝牙连接找不到设备
经常在测试的时候,发现第一次扫码连接正常,而第二次就死活都不进行设备扫描了。主要就是不再触发 onBluetoothDeviceFound 。原因是 Android 我们之前已经建立连接了,但是我们经常重新扫描的时候,并没有断开。因此建议我们手动关闭连接然后再重新打开连接。
wx.closeBluetoothAdapter();
wx.openBluetoothAdapter();
处理蓝牙断开
这是两方面的问题,一个蓝牙未打开,而是设备工作中突然断掉(停电或者手误关闭等)
微信有两个 API 可以帮助你解决这个问题:
// 处理手机蓝牙打开和关闭
wx.onBluetoothAdapterStateChange(function (res) {
console.log('onBluetoothAdapterStateChange', res);
if (res.available) {
// @TODO
}
});
// 处理设备突然断掉
wx.onBLEConnectionStateChange((res) => {
if (!res.connected && this.data.connected) {
@TODO
}
});
微信小程序连接蓝牙打印机打印二维码和内容源码下载
- 上一篇:扫描二维码跳转微信小程序指定页面
- 下一篇:关注微信公众号自动回复跳转小程序
精品好课