如何解决.NET Maui 7中蓝牙POS打印机打印BMP图片乱码问题?
问题描述
使用Android蓝牙嵌入式POS打印机,基于.NET Maui 7开发应用,尝试打印黑白BMP图片时,代码可正常运行但打印结果为乱码字符。
目标图片为:黑白二维码搭配文字的组合图
打印结果为:一串无意义的乱码字符
使用的代码如下:
public async Task PrintImage(string deviceName) { byte[] imageData; string someUrl = "https://..."; using (var webClient = new WebClient()) { imageData = webClient.DownloadData(someUrl); } byte[] SELECT_BIT_IMAGE_MODE = { 0x1B, 0x2A, 33, (byte)255, 0 }; using (BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter) { MemoryStream stream = new MemoryStream(); BluetoothDevice device = (from bd in bluetoothAdapter?.BondedDevices where bd?.Name == deviceName select bd).FirstOrDefault(); try { using (BluetoothSocket bluetoothSocket = device?. CreateRfcommSocketToServiceRecord( UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"))) { bluetoothSocket?.Connect(); //stream.Write(imageData, 0, imageData.Length); stream.Write(SELECT_BIT_IMAGE_MODE, 0, SELECT_BIT_IMAGE_MODE.Length); stream.Write(imageData, 0, imageData.Length); var bytes = stream.ToArray(); bluetoothSocket?.OutputStream.Write(bytes, 0, bytes.Length); bluetoothSocket.Close(); } } catch (Exception exp) { throw exp; } } }
问题原因
- BMP原始数据无法被打印机识别:POS打印机的位图打印指令需要的是单色位图的像素位流(仅包含像素信息),而完整的BMP文件包含文件头、信息头等非像素数据,直接写入会被打印机当作乱码处理。
- 打印指令参数不匹配:当前使用的
SELECT_BIT_IMAGE_MODE指令参数(尤其是宽度参数)不符合打印机要求,导致无法正确解析后续数据。
解决方案
需要将BMP图片转换为打印机可识别的单色像素流,并修正打印指令参数,具体步骤如下:
步骤1:解码BMP并转换为单色位图
将下载的BMP字节数据解码为Bitmap对象,再转换为1位单色位图(二值化),提取纯像素信息。
步骤2:生成符合ESC/POS标准的打印指令
根据图片宽度计算指令的宽度参数,使用ESC * 33 nL nH格式的24点密度位图指令(适配多数POS打印机)。
步骤3:写入指令与像素数据到蓝牙流
修改后的代码示例:
using Android.Graphics; using System.Buffers.Binary; public async Task PrintImage(string deviceName) { byte[] imageData; string someUrl = "https://..."; using (var webClient = new WebClient()) { imageData = webClient.DownloadData(someUrl); } // 解码BMP图片 Bitmap originalBitmap; using (var ms = new MemoryStream(imageData)) { originalBitmap = BitmapFactory.DecodeStream(ms); } // 转换为1位单色位图(二值化) var monoBitmap = ConvertToMonochrome(originalBitmap); // 计算打印机所需宽度参数:每列对应8像素,列数向上取整 int widthInColumns = (monoBitmap.Width + 7) / 8; byte nL = (byte)(widthInColumns & 0xFF); byte nH = (byte)((widthInColumns >> 8) & 0xFF); // ESC/POS 24点密度位图指令 byte[] selectBitImageMode = { 0x1B, 0x2A, 33, nL, nH }; // 生成打印机可识别的像素流(按24像素高度分块) var pixelStream = new MemoryStream(); for (int y = 0; y < monoBitmap.Height; y += 24) { pixelStream.Write(selectBitImageMode, 0, selectBitImageMode.Length); // 生成当前块的像素数据 for (int x = 0; x < widthInColumns; x++) { for (int k = 0; k < 3; k++) // 24像素对应3字节 { byte pixelByte = 0; for (int i = 0; i < 8; i++) { int pixelY = y + k * 8 + i; if (pixelY >= monoBitmap.Height) break; int pixelX = x * 8 + i; if (pixelX >= monoBitmap.Width) continue; Color color = monoBitmap.GetPixel(pixelX, pixelY); // 黑色像素对应位设为1(可根据打印机调整反转) if (color.R < 128) pixelByte |= (byte)(1 << (7 - i)); } pixelStream.WriteByte(pixelByte); } } pixelStream.WriteByte(0x0A); // 换行 } using (BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter) { BluetoothDevice device = bluetoothAdapter?.BondedDevices?.FirstOrDefault(bd => bd?.Name == deviceName); if (device == null) throw new Exception("未找到绑定的蓝牙设备"); try { using (var bluetoothSocket = device.CreateRfcommSocketToServiceRecord(UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"))) { await bluetoothSocket.ConnectAsync(); var bytes = pixelStream.ToArray(); await bluetoothSocket.OutputStream.WriteAsync(bytes, 0, bytes.Length); await bluetoothSocket.OutputStream.FlushAsync(); bluetoothSocket.Close(); } } catch (Exception exp) { throw exp; } } } // 将Bitmap转换为单色二值位图 private Bitmap ConvertToMonochrome(Bitmap bitmap) { var monoBitmap = Bitmap.CreateBitmap(bitmap.Width, bitmap.Height, Bitmap.Config.Argb8888); using (var canvas = new Canvas(monoBitmap)) { var paint = new Paint(); var colorMatrix = new ColorMatrix(); colorMatrix.SetSaturation(0); // 转为灰度图 paint.SetColorFilter(new ColorMatrixColorFilter(colorMatrix)); canvas.DrawBitmap(bitmap, 0, 0, paint); } // 二值化处理:灰度值低于128设为黑色,否则白色 for (int x = 0; x < monoBitmap.Width; x++) { for (int y = 0; y < monoBitmap.Height; y++) { Color color = monoBitmap.GetPixel(x, y); int gray = (color.R + color.G + color.B) / 3; monoBitmap.SetPixel(x, y, gray < 128 ? Color.Black : Color.White); } } return monoBitmap; }
额外注意事项
- 指令兼容性:不同品牌打印机的ESC/POS指令可能有差异,若仍有问题请查阅打印机官方手册确认位图指令格式。
- 像素位序调整:若打印图片反转或错位,可尝试修改
pixelByte |= (byte)(1 << (7 - i))为pixelByte |= (byte)(1 << i)调整位序。 - 权限配置:Android 12及以上需申请
BLUETOOTH_CONNECT、BLUETOOTH_SCAN权限,低版本需申请BLUETOOTH、BLUETOOTH_ADMIN权限。
内容的提问来源于stack exchange,提问作者Thomas Carlton
相关产品推荐
相关产品推荐

