如何在 Android 的蓝牙打印机上打印图像?

Posted

技术标签:

【中文标题】如何在 Android 的蓝牙打印机上打印图像?【英文标题】:How can I print an image on a Bluetooth printer in Android? 【发布时间】:2013-01-09 21:41:44 【问题描述】:

我必须在热敏蓝牙打印机上打印一些数据,我正在这样做:

String message="abcdef any message 12345";
byte[] send;
send = message.getBytes();
mService.write(send);

它适用于文本,但不适用于图像。我想我需要获取图像数据的byte[]。我尝试以这种方式获取图像的数据:

Bitmap bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.qrcode);
ByteArrayOutputStream stream=new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream);
byte[] image=stream.toByteArray();

不幸的是,打印机打印了很多奇怪的字符(大约 50 厘米的纸)。我不知道如何打印图像。

我想尝试获取位图的像素,然后将其转换为byte[] 并发送它,但我不知道该怎么做。

谢谢

更新:

经过这么长时间,我正在这样做:我有一个名为 print_image(String file) 的方法,它获取我要打印的图像的路径:

private void print_image(String file) 
    File fl = new File(file);
    if (fl.exists()) 
        Bitmap bmp = BitmapFactory.decodeFile(file);
        convertBitmap(bmp);
        mService.write(PrinterCommands.SET_LINE_SPACING_24);

        int offset = 0;
        while (offset < bmp.getHeight()) 
            mService.write(PrinterCommands.SELECT_BIT_IMAGE_MODE);
            for (int x = 0; x < bmp.getWidth(); ++x) 

                for (int k = 0; k < 3; ++k) 

                    byte slice = 0;
                    for (int b = 0; b < 8; ++b) 
                        int y = (((offset / 8) + k) * 8) + b;
                        int i = (y * bmp.getWidth()) + x;
                        boolean v = false;
                        if (i < dots.length()) 
                            v = dots.get(i);
                        
                        slice |= (byte) ((v ? 1 : 0) << (7 - b));
                    
                    mService.write(slice);
                
            
            offset += 24;
            mService.write(PrinterCommands.FEED_LINE);
            mService.write(PrinterCommands.FEED_LINE);          
            mService.write(PrinterCommands.FEED_LINE);
            mService.write(PrinterCommands.FEED_LINE);
            mService.write(PrinterCommands.FEED_LINE);
            mService.write(PrinterCommands.FEED_LINE);
        
        mService.write(PrinterCommands.SET_LINE_SPACING_30);


     else 
        Toast.makeText(this, "file doesn't exists", Toast.LENGTH_SHORT)
                .show();
    

我是根据这个post做的

这是 PrinterCommands 类:

public class PrinterCommands 
public static final byte[] INIT = 27, 64;
public static byte[] FEED_LINE = 10;

public static byte[] SELECT_FONT_A = 27, 33, 0;

public static byte[] SET_BAR_CODE_HEIGHT = 29, 104, 100;
public static byte[] PRINT_BAR_CODE_1 = 29, 107, 2;
public static byte[] SEND_NULL_BYTE = 0x00;

public static byte[] SELECT_PRINT_SHEET = 0x1B, 0x63, 0x30, 0x02;
public static byte[] FEED_PAPER_AND_CUT = 0x1D, 0x56, 66, 0x00;

public static byte[] SELECT_CYRILLIC_CHARACTER_CODE_TABLE = 0x1B, 0x74, 0x11;

public static byte[] SELECT_BIT_IMAGE_MODE = 0x1B, 0x2A, 33, -128, 0;
public static byte[] SET_LINE_SPACING_24 = 0x1B, 0x33, 24;
public static byte[] SET_LINE_SPACING_30 = 0x1B, 0x33, 30;

public static byte[] TRANSMIT_DLE_PRINTER_STATUS = 0x10, 0x04, 0x01;
public static byte[] TRANSMIT_DLE_OFFLINE_PRINTER_STATUS = 0x10, 0x04, 0x02;
public static byte[] TRANSMIT_DLE_ERROR_STATUS = 0x10, 0x04, 0x03;
public static byte[] TRANSMIT_DLE_ROLL_PAPER_SENSOR_STATUS = 0x10, 0x04, 0x04;

正如在 print_image 方法中看到的,我正在调用一个名为 convertBitmap 的方法,并且我正在发送一个位图,代码如下:

   public String convertBitmap(Bitmap inputBitmap) 

    mWidth = inputBitmap.getWidth();
    mHeight = inputBitmap.getHeight();

    convertArgbToGrayscale(inputBitmap, mWidth, mHeight);
    mStatus = "ok";
    return mStatus;



private void convertArgbToGrayscale(Bitmap bmpOriginal, int width,
        int height) 
    int pixel;
    int k = 0;
    int B = 0, G = 0, R = 0;
    dots = new BitSet();
    try 

        for (int x = 0; x < height; x++) 
            for (int y = 0; y < width; y++) 
                // get one pixel color
                pixel = bmpOriginal.getPixel(y, x);

                // retrieve color of all channels
                R = Color.red(pixel);
                G = Color.green(pixel);
                B = Color.blue(pixel);
                // take conversion up to one single value by calculating
                // pixel intensity.
                R = G = B = (int) (0.299 * R + 0.587 * G + 0.114 * B);
                // set bit into bitset, by calculating the pixel's luma
                if (R < 55)                        
                    dots.set(k);//this is the bitset that i'm printing
                
                k++;

            


        


     catch (Exception e) 
        // TODO: handle exception
        Log.e(TAG, e.toString());
    

这是我正在使用的printer,分辨率:8 点/毫米,576 点/行

这就是我喜欢做的事情(我使用同一台打印机,但使用从 Play 商店下载的应用程序)

这就是我现在得到的

更接近:

更接近2:

可以看到一小部分图像,所以我认为我更接近可以打印图像......

我使用的图片是这个 (576x95):

这是转换后的图像(我用上面的代码转换它):

所以,答案是:我做错了什么?,我认为错误在这个命令中:

  public static byte[] SELECT_BIT_IMAGE_MODE = 0x1B, 0x2A, 33, -128, 0;

但是,我怎样才能为我的图像计算正确的值?,谢谢

【问题讨论】:

是的,我发送它的方式与第一个snipplet mService.write(image) 相同,打印机有一个SDK,但它不编译,所以我不知道该怎么做。 如果你有 SDK 的源代码,你可以看看他们是怎么做的。 现在我反编译它,他们使用一种方法返回 byte[] 数组,但它不起作用,我实现了该方法但是当我在平板电脑上运行应用程序时,它死了,生成强制关闭错误... `` 我只有 .jar 库,这是由打印机按比例分配的 SDK……但我没有使用它,因为它不适合我,它不编译.. .我在堆栈跟踪中没有错误...当我尝试打印图像时,它可以正常打印,但不是图像,打印一些陌生字符... @nlsbshtr 我推荐 Bixolon SPP-R200。但是我们没有使用官方的 SDK 进行打印,而是我们实现了自己的配对和打印程序。 【参考方案1】:

这对我有用:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inTargetDensity = 200;
options.inDensity = 200;
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), img, options);

在使用 BitmapFactory.Options 之前,我只能打印 60X60 尺寸的图像,现在我也可以打印更大尺寸的图像了。

【讨论】:

【参考方案2】:

我解决了将位图转换为字节数组的问题。请记住,您的图片必须是黑白格式。

完整源代码: https://github.com/imrankst1221/Thermal-Printer-in-android

 public void printPhoto() 
        try 
            Bitmap bmp = BitmapFactory.decodeResource(getResources(),
                    R.drawable.img);
            if(bmp!=null)
                byte[] command = Utils.decodeBitmap(bmp);
                printText(command);
            else
                Log.e("Print Photo error", "the file isn't exists");
            
         catch (Exception e) 
            e.printStackTrace();
            Log.e("PrintTools", "the file isn't exists");
        
    

【讨论】:

为什么有宽高限制? 是否可以打印宽于 255 像素的图像? @daneejela 的回答是否定的,但如果您选择的打印机支持,那么它可以工作。 我应该怎么做只是粘贴你的代码?那么它会工作吗?因为 Utils 出现红色错误,我应该如何更改它? @MdImranChoudhury 实用程序为此设备模块运行。因此,如果您使用的是不同的版本,那么您可能需要进行更改。【参考方案3】:

我是 ESC/POS 的新手,并且正在为此苦苦挣扎。我遇到了这个页面,它似乎有一些有用的功能:http://code.taobao.org/p/printer/src/trunk/prtest/src/com/enjar/plugins/PrintTools_58mm.java 虽然是中文的,但可能值得一试。如果有人想通了,我也想开悟……

【讨论】:

谢谢,这些代码对于在 BT 打印机中打印图像很有用,我改进了用于图像打印的代码,所以如果您需要一些提示,我可以为您提供帮助。【参考方案4】:

我也试过这个,我找到了自己的解决方案,我想我知道SELECT_BIT_IMAGE_MODE 命令是如何工作的。

PrinterCommands类中的命令public static byte[] SELECT_BIT_IMAGE_MODE = 0x1B, 0x2A, 33, 255, 3;是图片打印的POS命令。

前两个非常标准,接下来的三个确定要打印的图像的模式和尺寸。为了这个解决方案,我们假设第二个元素(33,我们的索引为零)总是 33。

该字节[] 的最后两个元素是指您要打印的图像的 Width(以像素为单位) 属性,元素 3 有时称为 nL 和元素 4有时称为nH。它们实际上都指的是宽度,nLLow BytenHHigh Byte。这意味着我们最多可以拥有一个宽度为 1111 1111 1111 1111b(二进制)即 65535d(十进制)的图像,尽管我还没有尝试过。如果 nL 或 nH 未设置为正确的值,则会与图像一起打印垃圾字符。

不知何故,Android 文档告诉我们字节数组中一个字节的值的限制是 -128 和 +127,当我尝试输入 255 时,Eclipse 要求我将其转换为字节。

无论如何,回到 nL 和 nW,对于您的情况,您有一个宽度为 576 的图像,如果我们将 576 转换为二进制,我们会得到两个字节,如下所示:

0000 0010 0100 0000

在这种情况下,低字节是0100 0000,而高字节是0000 0010。将其转换回十进制,我们得到nL = 64nH = 2

在我的例子中,我打印了一个宽度为 330 像素的图像,将 330 转换为二进制我们得到:

0000 0001 0100 1010

在这种情况下,低字节是0100 1010,高字节是0000 0001。转换为十进制,我们得到nL = 74nH = 1

有关更多信息,请查看以下文档/教程:

Star Asia Mobile Printer Documentation

ECS-POS programming guide - really extensive

Another Documentation

The expanded version of the code above, with more explanation

Explanation of the code above

希望这些有所帮助。

【讨论】:

感谢您的回答,我使用此代码来初始化我的 priter:public static byte[] SELECT_BIT_IMAGE_MODE = 0x1B, 0x2A, 33, (byte) 255, 3 ;,它对我来说没问题 这个答案也对我有帮助。我在recaipt上打印蓝牙QRcode 150x150,至少可以工作。它在左侧打印。如果可能的话,我的问题是如何将 printet 图像居中? 0x1B,0x33,0x00 在打印位图以使图像居中之前加载此命令@Vranilac【参考方案5】:

解决了!,我正在做一个错误的打印机初始化...正确的方法是:

 public static byte[] SELECT_BIT_IMAGE_MODE = 0x1B, 0x2A, 33, 255, 3;

所以,通过这种方式,图像打印得非常好

【讨论】:

您好,我实现了上面的代码并用您的解决方案替换了它,但我仍然在纸上打印了一些奇怪的字符。请帮忙。提前致谢。 如果你能帮助我,那就太好了。 对不起,我写错了我的邮件,是这样的:leonardo.sapuy@hotmail.com 我想通了,请参考我的答案以获得详细版本。 @Leonardo Sapuy 我真的需要你的小帮助......请给我发电子邮件 usmizpassion@gmail.com【参考方案6】:

使用此代码:

public static void print(Context context) 

    String examplePath = "file:///sdcard/dcim/Camera/20111210_181524.jpg";

    Intent sendIntent = new Intent(Intent.ACTION_SEND);
    sendIntent.setType("image/jpeg");
    sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Photo");
    sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(examplePath));
    sendIntent.putExtra(Intent.EXTRA_TEXT, "Enjoy the photo");
    context.startActivity(Intent.createChooser(sendIntent, "Email:"));

【讨论】:

抱歉,我不喜欢发送图像...我想使用 esc/pos 命令打印图像,是这样...还是谢谢【参考方案7】:

我知道 evolute 和 AMDL 蓝牙打印机。首先阅读打印机的协议定义文档,告诉您设备需要哪些特定字节-

public void connect() throws Exception 


    BluetoothDevice printer = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(connParams);

    Method m = printer.getClass().getMethod("createInsecureRfcommSocket",new Class[]  int.class );
    sock = (BluetoothSocket)m.invoke(printer, Integer.valueOf(1));
    sock.connect();
    os=sock.getOutputStream();
    in=sock.getInputStream();


通过上面的代码连接后得到socket的输出流。然后通过打印机提供的工具将你的图像转换为相应的字节你得到类似的东西

public byte[] Packet1=
        (byte)0X8A,(byte)0XC6,(byte)0X94,(byte)0XF4,(byte)0X0B,(byte)0X5E,(byte)0X30,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X04,(byte)0X24,(byte)0X01,(byte)0X0C,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X01,(byte)0X08,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X04,(byte)0X24,(byte)0X05,(byte)0X0C,(byte)0X00,(byte)0X60,(byte)0X00,(byte)0X18,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X30,(byte)0X1E,(byte)0X10,(byte)0X60,(byte)0X00,(byte)0X18,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X70,(byte)0X3F,(byte)0X18,(byte)0XF0,(byte)0X00,(byte)0X3E,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X70,(byte)0X3C,(byte)0X39,(byte)0XF1,(byte)0X80,(byte)0X3E,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0XF8,(byte)0X7C,(byte)0X9F,(byte)0XF1,(byte)0X80,(byte)0X7F,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0XF9,(byte)0X9E,(byte)0X1C,(byte)0XFF,(byte)0XC2,(byte)0X7E,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0XF9,(byte)0X9E,(byte)0X1C,(byte)0XE7,(byte)0XE2,(byte)0X7E,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0XFB,(byte)0X1E,(byte)0X1C,(byte)0XFF,(byte)0XE7,(byte)0XBE,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X7B,(byte)0X16,(byte)0X1C,(byte)0XFF,(byte)0XDF,(byte)0X3E,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X71,(byte)0X12,(byte)0X1C,(byte)0XE7,(byte)0XF7,(byte)0X34,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X51,(byte)0X12,(byte)0X1C,(byte)0XF7,(byte)0XF7,(byte)0X24,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X49,(byte)0X12,(byte)0X1C,(byte)0XFF,(byte)0XF3,(byte)0X24,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X49,(byte)0X12,(byte)0X3F,(byte)0XFD,(byte)0XF3,(byte)0X24,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0X49,(byte)0X96,(byte)0X3F,(byte)0XFC,(byte)0XF3,(byte)0X24,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X05,(byte)0X49,(byte)0X80,(byte)0X00,(byte)0X08,(byte)0X10,(byte)0X5E,(byte)0X28,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X30,(byte)0X25,(byte)
        0X01,(byte)0X5E,(byte)0X03,(byte)0X24,(byte)0X06,(byte)0XE0,(byte)0X74,(byte)0XA9,(byte)0X33,(byte)0X23,(byte)0X26,(byte)0X5E,(byte)0X27,(byte)0X25,(byte)0X04
        ;

其中 8A 是起始字节 C6 是模式字节(智能卡、刷卡和指纹不同),94 是字体字节,最后一个字节 04 是结束字节,告诉硬件这是数据包的结尾。取决于大小图像你得到几个长度为 256 字节的数据包(大多数打印机)。将它们写入 outputStream。

os.write(Packet1)

【讨论】:

我现在正在将图像转换为单色位图,因此我正在创建一个包含 1 和 0(它代表黑色或白色)的位集,但图像打印不正常,只打印图像的一小部分... 您需要将图像转换为其十六进制表示,然后将这些十六进制数据转换为字节。设备只支持字节格式。还给我市场应用程序的链接,通过它你可以得到打印。看看我能不能从中得到什么。 @mjosh 您好,请看一下我的问题:***.com/questions/16982485/…【参考方案8】:

编辑:根据阅读您的问题进行更新:https://***.com/questions/16597789/print-bitmap-on-esc-pos-printer-java

我将假设您要打印的打印机与上述相同,即 Rego 热敏打印机。正如您所指出的,这支持ESC/POS Page Description Language。


打印机将流向它们的数据解释为标记的文档(以类似于浏览器解释 html 的方式)。在某些情况下,打印机实际上将文档作为程序(例如 PostScript)运行。链接:Page Description Languages.

常用语言有:

您的:ESC/POS。 PostScript PCL ZPL

您需要阅读打印机的规格以确定要使用的语言 - 如果您需要支持任何打印机,那么您的工作量很大:(

在 ESC/POS 中,您将需要使用 GS v 0 命令(记录在 p33 中)。为此,您可以通过串行链接发送字符 0x1D7630,后跟一组参数:

ASCII:       Gs   v  0 
Decimal:     29 118 48 m xL xH yL yH [d]k 
Hexadecimal: 1D  76 30 m xL xH yL yH [d]k 

参数定义:

米: 0,48:正常模式(1:1 比例) 1,49:双宽 2,50:双高 3,51: 双倍宽 + 双倍高 xL, xH 指定位图水平方向的 (xL + xH × 256) 个字节。 yL, yH 指定位图垂直方向的 (yL + yH × 256) 个点。 [d]k 指定位图数据(光栅格式)。 k 表示位图像数据的数量。 k是解释参数;因此,它不需要传输。

注意事项:

当数据 [d]k 为 1 时,指定位打印为 1 而不是打印为 0。 如果光栅位图超过一行打印区域,则不会打印超出的数据。 无论ESC 2 或ESC 3 的设置如何,此命令都会执行打印位图所需的进纸量。 打印位图后,此命令将打印位置设置为行首,并清除缓冲区。 执行该命令时,数据同步传输和打印。因此不需要其他打印命令。

还有几个更广泛的阐述:

http://nicholas.piasecki.name/blog/2009/12/sending-a-bit-image-to-an-epson-tm-t88iii-receipt-printer-using-c-and-escpos/ On SO in C#。虽然不是 Java,但它足以成为模板。

很遗憾,Android 中没有打印机 API。如果您对此有强烈的感觉,请遵循以下问题:

https://code.google.com/p/android/issues/detail?id=40486 https://code.google.com/p/android/issues/detail?id=1148

【讨论】:

感谢您的回答...现在我正在打印图像的一小部分,我的打印机兼容 ESC/POS 命令,并且具有 576 点/线分辨率...我找到this,是 Nicholas Piasecki 的帖子的 java 翻译...我在我的项目中实现了该源,所以现在我正在打印我的图像的一小部分...但无论如何它无法打印完整的图像.我认为这是一个不好的命令使用,我正在使用这个命令在图形模式下初始化打印机:0x1B, 0x2A, 33, -128, 0 但我认为我做错了......我的图像分辨率为 576 x 96 像素,你能告诉我打印机初始化是否正确,或者我必须做什么改代码?? 我要上床睡觉了,但如果你能更全面地描述发生了什么和没有发生什么,那将非常有用。例如,产生了多少行输出?每行都打印全宽吗?打印像素的线条是完美的还是错误的?你能显示发送到打印机的数据的十六进制转储吗? 嗨,安德鲁,我编辑了答案,所以你可以在那里看到我到底在做什么,非常感谢 听不懂你在说什么。答案需要编辑。不太明白。

以上是关于如何在 Android 的蓝牙打印机上打印图像?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Android 上使用 Python 通过蓝牙发送 ESC 打印命令?

如何在蓝牙 POS 打印机中打印右对齐的文本?

Flutter蓝牙热敏POS打印机

如何管理已知的打印机?

Android蓝牙打印

如何用蓝牙小票打印机打印小票票据?