You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决.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;
        }
    }
}
问题原因
  1. BMP原始数据无法被打印机识别:POS打印机的位图打印指令需要的是单色位图的像素位流(仅包含像素信息),而完整的BMP文件包含文件头、信息头等非像素数据,直接写入会被打印机当作乱码处理。
  2. 打印指令参数不匹配:当前使用的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:24:56