我该如何在Android中将图片打印到蓝牙打印机上?

57

我需要将一些数据打印到热敏蓝牙打印机上,我正在使用以下方法:

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();
    }
}

我基于这篇文章完成了它。

这是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());
    }
}

这是我正在使用的打印机,分辨率为:8个点/毫米,每行576个点。
这是我喜欢做的事情(我用同一台打印机和从Play商店下载的应用程序完成): 我想要打印的图像 这是我现在得到的结果: 我的打印尝试 更近一步: 更近一步的部分 再次更近一步: 输入图像描述 只有图像的一小部分可以看到,所以我认为离打印图像更近了...
我使用的图像是这个(576x95):输入图像描述 这是转换后的图像(我正在使用上面的代码进行转换): 转换后的图像 反转 所以,答案是:我错在哪里?我认为错误在于这个命令:
  public static byte[] SELECT_BIT_IMAGE_MODE = {0x1B, 0x2A, 33, -128, 0};

但是,我该如何计算出我的图片的正确值呢?谢谢。

是的,我以与第一个片段相同的方式发送它 mService.write(image),打印机有一个SDK但它无法编译,所以我不知道该怎么做。 - Leonardo Sapuy
我正在将图像转换为单色BMP,按照你说的做... 但我需要获取该BMP并打印出来... 你知道如何做吗?或者可以给我一个使用bixolon SDK的示例,我正在使用REGO打印机,但它的SDK无法正常工作。 - Leonardo Sapuy
@LeonardoSapuy 你好,能否帮我解决这个问题:https://dev59.com/EHPYa4cB1Zd3GeqPeQ3E 我也遇到了类似的问题 :( - beerBear
不好意思,如果您想要的话,我可以给您发送测试项目。 - Leonardo Sapuy
嗨,Leonardo Sapuy,你可以分享一下你是如何解决这个问题的吗? - pavol.franek
显示剩余8条评论
8个回答

26
我将通过将位图转换为字节数组来解决此问题。请记住,您的图像必须是黑白格式。
完整的源代码请查看: https://github.com/imrankst1221/Thermal-Printer-in-Android

enter image description here

 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");
        }
    }

5
为什么它有宽度和高度的限制? - mr5
2
能否打印一张宽度超过255像素的图片? - daneejela
1
你好 @MdImranChoudhury,有时 decodeBitmap(bmp) 会返回 null。 - Deepak yogi
你是天使,它完美地工作了。谢谢!! - Sam Chen
你开始使用 ESC *,但后来用 GS v 0 解决了它。我没有测试过前者,但后者给我一个微小的图像,第一列左侧畸变。 - vesperto
显示剩余9条评论

13

我也尝试了这个方法,找到了自己的解决方案,并且我认为我弄清楚了SELECT_BIT_IMAGE_MODE命令的工作原理。

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

前两个字节很标准,接下来的三个字节决定要打印的图像模式和尺寸。为了解决问题,让我们假设第二个元素(33,从零开始索引)始终为33。

该byte[]的最后两个元素是指所需打印的宽度(以像素为单位)属性,第3个元素有时被称为nL,第4个元素有时被称为nH。实际上,它们都是在引用宽度,nL低字节,而nH高字节。这意味着我们最多可以拥有宽度为1111 1111 1111 1111b(二进制)的图像,即65535d(十进制),尽管我还没有尝试过。如果未将nL或nH设置为正确的值,则会在图像旁边打印垃圾字符。

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

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

0000 0010 0100 0000

在这种情况下,低字节为0100 0000,高字节为0000 0010。将其转换回十进制,我们得到nL = 64nH = 2
在我的情况下,我打印了一张宽度为330px的图像,将330转换为二进制,我们得到:0000 0001 0100 1010 在这种情况下,低字节为0100 1010,高字节为0000 0001。将其转换为十进制,我们得到nL = 74nH = 1
有关更多信息,请查看以下文档/教程:
- Star Asia Mobile Printer文档 - ECS-POS编程指南-非常全面 - 另一个文档 - 上述代码的扩展版本,含更多解释 - 上述代码的解释 希望这些对您有所帮助。

2
谢谢你的回答,我使用这段代码来初始化我的打印机:public static byte[] SELECT_BIT_IMAGE_MODE = { 0x1B, 0x2A, 33, (byte) 255, 3 }; 对我来说它运行得很好。 - Leonardo Sapuy
这个答案也帮了我。我在收据上通过蓝牙打印了一个150x150的QR码,至少它能够工作。它被打印在左侧。我的问题是如何居中打印图像,如果可能的话? - Vranilac
在打印位图之前,加载{0x1B,0x33,0x00}命令以使图像居中。@Vranilac - Peter

12

问题已解决!我一开始初始化打印机的方法是错误的... 正确的方式是:

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

所以,通过这种方式,图像可以完全正常地打印出来


嗨,我已经实现了上面的代码并用你的解决方案替换了它,但是仍然在纸上打印出一些奇怪的字符。请帮忙。先谢谢了。 - Ankit
如果您能帮助我,那将是非常好的。 - Ankit
我已经想通了,请参考我的答案以获取更详细的版本。 - Razgriz
@Leonardo Sapuy,我真的需要你的小帮助……请给我发电子邮件usmizpassion@gmail.com - user2768215
嗨,@LeonardoSapuy,你能告诉我你原始代码中的mService是什么吗? - sups
显示剩余2条评论

9

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

我假设你正在打印的打印机与上文提到的那台相同,即Rego热敏打印机。正如你所指出的那样,它支持ESC/POS页面描述语言


打印机将流向它们的数据解释为标记文档(类似于浏览器解释HTML的方式)。在某些情况下,打印机会像运行程序一样直接运行文档(例如PostScript)。链接:页面描述语言
常见的语言包括:
- 您的ESC/POS。 - PostScript - PCL - ZPL 您需要阅读打印机规格以确定使用哪种语言 - 如果您需要支持任何打印机,则需要完成非常庞大的工作:(
在ESC/POS中,您需要使用GS v 0命令(文档在第33页记录)。您可以通过发送字符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 

参数定义:

  • m:
    • 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进行设置。
  • 打印位图后,此命令将打印位置设置为该行的开头,并清除缓冲区。
  • 执行此命令时,数据会同步传输和打印。因此,不需要其他打印命令。

还有几篇更详细的说明:


很遗憾,Android中没有打印机API。如果您对此感到强烈关注,请关注以下问题:

感谢您的回答...现在我正在打印图像的一小部分,我的打印机兼容ESC/POS命令,并且具有576点/行的分辨率...我发现了这个,是尼古拉斯·彼亚塞基(Nicholas Piasecki)帖子的Java翻译...我在我的项目中实现了该源代码,所以现在我正在打印我图像的一小部分...但无论如何它都无法打印完整的图像。 我认为这是错误的命令使用,我正在使用这个命令初始化打印机的图形模式:0x1B, 0x2A, 33, -128, 0 - Leonardo Sapuy
但是我认为我做错了...我的图像具有576 x 96像素的分辨率,你能告诉我是否正确初始化打印机,或者代码中需要改变什么吗? - Leonardo Sapuy
2
我即将要睡觉了,但如果您能提供更全面的描述,那将非常有用。例如,输出了多少行?每行是否都打印了完整的宽度?这些行是否以像素完美或错误的方式打印?您能展示一下发送到打印机的数据的十六进制转储吗? - Andrew Alcock
嗨,安德鲁,我编辑了答案,所以你可以看到我正在做什么,提前感谢。 - Leonardo Sapuy
无法理解你的意思..答案需要修改。理解不够清楚。 - Mohammed Shareef C
显示剩余2条评论

3

谢谢,这些代码可以用于在BT打印机中打印图像,我改进了我的图像打印代码,如果你需要一些技巧,我可以帮助你。 - Leonardo Sapuy

2
我知道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();

}

通过以上代码连接后,您会获得套接字的输出流。然后通过打印机提供的工具将图像转换为相应的字节,您会得到类似于以下内容:

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字节的数据包(大多数打印机)。将它们写入输出流。

os.write(Packet1)

我现在正在将图像转换为单色位图,因此我正在创建一个包含1和0的位集(它表示黑色或白色),但是图像打印不正确,只打印了一小部分图像... - Leonardo Sapuy
你需要将图像转换为其十六进制表示,然后将这些十六进制数据转换为字节。设备只能理解字节格式。同时,请给我提供获取打印的市场应用程序链接,看看我能否从中获得任何信息。 - mihirjoshi
@mjosh您好,能否请您看一下我的问题:https://dev59.com/EHPYa4cB1Zd3GeqPeQ3E - beerBear

0
这对我有效:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTargetDensity = 200;
options.inDensity = 200;
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), img, options);

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


-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:"));
}

2
抱歉,但我不喜欢发送图片...我想使用ESC/POS命令打印图像,可以吗...无论如何感谢您。 - Leonardo Sapuy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接