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

Xamarin POS系统集成蓝牙打印机打印图片失败问题求助

Xamarin POS系统蓝牙打印机图片打印异常问题

我在Xamarin开发的POS系统中,纯文本打印功能正常,但图片打印出现异常。将图片转为字节数组发送给蓝牙打印机后无法正常输出,打印出的logo效果如下:

打印出的异常logo

使用的蓝牙打印机设备如下:

蓝牙打印机设备

以下是当前使用的打印服务代码:

public class PrintServiceRenderer
{
    private static byte[] SELECT_BIT_IMAGE_MODE = { 0x1B, 0x2A, 33, 255, 0 };
    public IList<string> GetDeviceList()
    {
        using (BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter)
        {
            var btdevice = bluetoothAdapter?.BondedDevices.Select(i => i.Name).ToList();
            return btdevice;
        }
    }
    public async Task Print(string deviceName, byte[] imageData)
    {
        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);
                    var bytes = stream.ToArray();
                    bluetoothSocket?.OutputStream.Write(bytes, 0, bytes.Length);
                    bluetoothSocket.Close();
                }
            }
            catch (Exception exp)
            {
                throw exp;
            }
        }
    }
}

问题根源分析

  1. 指令顺序错误:当前代码先发送图片数据,再发送位图模式指令,打印机未进入位图解析模式就收到图片数据,无法正确解析。
  2. 位图指令参数不匹配:SELECT_BIT_IMAGE_MODE中的宽度参数(255, 0)对应255像素宽度,若你的打印机是80mm宽(标准384像素),该参数会导致图片显示异常。
  3. 图片数据格式不符合要求:打印机需要的是单色位图数据(1bit/像素,仅黑/白),而非原始JPG/PNG的字节数组,需先做格式转换。

修正后的代码实现

1. 添加图片转单色位图数据的工具方法

private byte[] ConvertImageToPrinterBitmap(Image image, int printerWidth = 384)
{
    // 调整图片尺寸适配打印机宽度
    var scaledImage = ScaleImage(image, printerWidth);
    
    // 转为灰度图再转单色位图
    using (var bitmap = new Bitmap(scaledImage))
    {
        var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), 
            System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
        
        var bytesPerRow = (bitmap.Width + 7) / 8; // 每行字节数(1bit/像素)
        var totalBytes = bytesPerRow * bitmap.Height;
        var result = new byte[totalBytes];
        
        unsafe
        {
            byte* ptr = (byte*)bitmapData.Scan0;
            int stride = bitmapData.Stride;
            
            for (int y = 0; y < bitmap.Height; y++)
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    // 取灰度值,低于阈值设为黑色(1),否则白色(0)
                    byte gray = ptr[y * stride + x * 3];
                    int bitPos = x % 8;
                    int bytePos = y * bytesPerRow + x / 8;
                    
                    if (gray < 128)
                    {
                        result[bytePos] |= (byte)(0x80 >> bitPos);
                    }
                }
            }
        }
        
        bitmap.UnlockBits(bitmapData);
        return result;
    }
}

private Image ScaleImage(Image image, int targetWidth)
{
    var ratio = (double)targetWidth / image.Width;
    var targetHeight = (int)(image.Height * ratio);
    var scaled = new Bitmap(targetWidth, targetHeight);
    
    using (var g = Graphics.FromImage(scaled))
    {
        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        g.DrawImage(image, 0, 0, targetWidth, targetHeight);
    }
    
    return scaled;
}

2. 修正Print方法的指令顺序与参数

public async Task Print(string deviceName, Image image)
{
    using (BluetoothAdapter bluetoothAdapter = BluetoothAdapter.DefaultAdapter)
    {
        if (bluetoothAdapter == null) throw new InvalidOperationException("蓝牙未启用");
        
        BluetoothDevice device = bluetoothAdapter.BondedDevices.FirstOrDefault(bd => bd?.Name == deviceName);
        if (device == null) throw new ArgumentException("未找到指定蓝牙设备");
        
        try
        {
            using (BluetoothSocket bluetoothSocket = device.CreateRfcommSocketToServiceRecord(
                UUID.FromString("00001101-0000-1000-8000-00805f9b34fb")))
            {
                await bluetoothSocket.ConnectAsync();
                
                // 转换图片为打印机兼容格式
                var bitmapData = ConvertImageToPrinterBitmap(image);
                int imageWidth = 384; // 80mm打印机标准宽度,根据你的设备调整
                
                // 构造正确的位图模式指令:ESC * m nL nH 数据
                // m=33 表示24点密度的位图模式
                byte nL = (byte)(imageWidth % 256);
                byte nH = (byte)(imageWidth / 256);
                byte[] selectBitImageMode = { 0x1B, 0x2A, 33, nL, nH };
                
                // 先发送模式指令,再发送图片数据
                await bluetoothSocket.OutputStream.WriteAsync(selectBitImageMode, 0, selectBitImageMode.Length);
                await bluetoothSocket.OutputStream.WriteAsync(bitmapData, 0, bitmapData.Length);
                
                // 发送换行指令,确保图片打印完成
                await bluetoothSocket.OutputStream.WriteAsync(new byte[] { 0x0A }, 0, 1);
                
                bluetoothSocket.Close();
            }
        }
        catch (Exception exp)
        {
            throw exp;
        }
    }
}

额外注意事项

  • 确认打印机的最大打印宽度,调整printerWidth参数(58mm打印机一般是203像素,80mm是384像素)。
  • 若使用Xamarin.Forms,需将共享项目中的Image转为平台特定的Bitmap(Android用Android.Graphics.Bitmap,iOS用UIKit.UIImage),可通过依赖服务实现跨平台处理。
  • 图片转换时的灰度阈值(128)可根据实际打印效果调整,优化黑白对比度。

内容的提问来源于stack exchange,提问作者Đức Khánh Hoàng

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 12:15:30