Android蓝牙打印机,带您真的了解各种打印格式。iOS开发之蓝牙/Socket链接小票打印机(一)

注意:本文原创,转载请注明出处。欢迎关注自我的
简书 。

必威体育 1

正文主要讲解蓝牙打印机在打印小票之经过中,如何打印各种常见格式。由于之前用调剂打印格式,但是苦于网上没详尽的讲课教程,无奈只好自给自足,自己包装了一个。如果各位盆友正在或者曾苦恼蓝牙打印机的起印格式,那么恭喜你,本篇博文就是公只要摸的。

前言

前公司发生个面向商户的花色,需要连接商户打印机打印小票底功用。于是对这点展开了就学研究,最后“顺利”的得了路求。这里要是对项目中因故到之知识点进行有总。这首文章要含有的连锁文化来:SocketCoreBluetooth网口小票打印机蓝牙小票打印机ESC/POS打印命令集图打印等。

什么是蓝牙打印机

然,你无看错。一开始兔子哥先来介绍一下啊是蓝牙打印机。。。好吧,这个就算交付百度了:

蓝牙打印机(Bluetooth
printer)就是以蓝牙技术运用在打印机及,摆脱打印机连线所带来的诸多不便,实现无线打印,可以减少桌面上使人不适的电缆,并且可用打印机远离主机任意搬动,摆放在房间中称之职。

概述

一切打印流程大致分可以为老三只步骤,①链子接打印机;②编辑排版打印内容;③发送数据给打印机;

①与③基于不同的打印机类型,我们而采取两样之链接方式。网口打印机通过Socket进行链接(需在同等局域网下),蓝牙打印机自然是经蓝牙进行链接。
②修排版打印内容,需要经过ESC/POS打印命令集来做,以下会进展连锁的牵线。

实际上,步骤②修排版打印内容,放到后台做是更为合理之,这样Android和iOS两端就避免了还如描绘编辑排版的代码,而且为会幸免排版上的差别。我们合作社呢是这样做的,所以步调②哪怕好变更呢自后台获取要打印的多寡。

大小票样式

必威体育 2

Paste_Image.png

这个微票格式基本就是最最广的了。这里面的各种格式,都可从蓝牙打印机的API里面找到。蓝牙打印机有诸多API,我将常用的给封装了转:PrintUtils.java

/**
 * 复位打印机
 */
public static final byte[] RESET = {0x1b, 0x40};

/**
 * 左对齐
 */
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};

/**
 * 中间对齐
 */
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};

/**
 * 右对齐
 */
public static final byte[] ALIGN_RIGHT = {0x1b, 0x61, 0x02};

/**
 * 选择加粗模式
 */
public static final byte[] BOLD = {0x1b, 0x45, 0x01};

/**
 * 取消加粗模式
 */
public static final byte[] BOLD_CANCEL = {0x1b, 0x45, 0x00};

/**
 * 宽高加倍
 */
public static final byte[] DOUBLE_HEIGHT_WIDTH = {0x1d, 0x21, 0x11};

/**
 * 宽加倍
 */
public static final byte[] DOUBLE_WIDTH = {0x1d, 0x21, 0x10};

/**
 * 高加倍
 */
public static final byte[] DOUBLE_HEIGHT = {0x1d, 0x21, 0x01};

/**
 * 字体不放大
 */
public static final byte[] NORMAL = {0x1d, 0x21, 0x00};

/**
 * 设置默认行间距
 */
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};

ESC/POS打印命令集

打印实现

打印小票,当然首先需连续蓝牙打印机。至于如何扫描打印机,如何连接,这个还是正经的蓝牙方式,网上资料啊杀多。因为本博文主要关心打印格式,所以是就不再赘述了。连接打印机后,需要从BluetoothSocket中获取OutputStream。然后搭下都是由此OutputStream来让打印机发送打印指令。

  • 装打印格式
    安装打印格式,就要以上面封装的那些指令了。

    /**
     * 设置打印格式
     *
     * @param command 格式指令
     */
    public static void selectCommand(byte[] command) {
        try {
            outputStream.write(command);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

因此法如下:

  PrintUtils.selectCommand(PrintUtils.RESET);
  PrintUtils.selectCommand(PrintUtils.LINE_SPACING_DEFAULT);
  PrintUtils.selectCommand(PrintUtils.ALIGN_CENTER);
  PrintUtils.selectCommand(PrintUtils.NORMAL);
  • 打印文字

    /**
     * 打印文字
     *
     * @param text 要打印的文字
     */
    public static void printText(String text) {
        try {
            byte[] data = text.getBytes("gbk");
            outputStream.write(data, 0, data.length);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

故此法如下:

  PrintUtils.printText("好吃的牛肉面" + "\n");

打印文字的时候,最后都要手动拼接一个 “\n” 用来换行。

简介

WPSON StandardCode for Printer
是EPSON公司协调创制的针式打印机的格指令集,现在既成为针式打印机控制语言事实上的工业标准。ESC/POS打印命令集是ESC打印控制命令的简化版,现在多数票据打印都用ESC/POS指令集。其强烈特点是:其中老大酷一部分发令都是以ESC控制入开始之均等拧代码。

打印机的型号种类有好多,不同的厂家也对那出品开了相应的定制。但是,ESC/POS指令集基本还见面支持。关于指令的详尽内容,网上发成千上万文档,另外每个品牌之官网,也会见时有发生对应的打印机指令文档提供下载。我们可以下载下来研究。这里大概介绍几栽常用之通令:

完美吗?

依据地方封装的代码,“貌似”是可以实现所有的打印样式了。是的,没毛病。因为上面既出打印格式的安,又出打印文字的用法。打印小票是从未问题了。but……

这种格式如何兑现?

必威体育 3

Paste_Image.png

这种格式为?

必威体育 4

Paste_Image.png

一对盆友可能会见说,这出甚问题之??? 并且被起了他们认为到的诠释:

  PrintUtils.printText("合计                           53.50" + "\n");
  PrintUtils.printText("抹零                            3.50" + "\n");
  PrintUtils.printText("项目            数量            金额" + "\n");

可是,完美吗?
乃也许认为人工加空格是足以“实现”需求。but……中间的空格,你知当加上多少吧?添加多了或少了,打印出来的结果还见面同样塌糊涂!并且注意小票及且是讲求针对联合的!合计、抹零左侧对伙同。金额右侧对合。项目、数量、金额就三排列都使主导对同。。看到此间,这个人工加空格的做法,还到也?

命介绍

证:一般打印机接受指令都支持三栽格式:ASCII、十进制、十六进制。

为自家一个完美的诠释!

“海参炒面,海参呢?给本人一个全面的解说!”
“我受海参,面是自炒的。完美不?”

正确,我们需要一个圆的诠释。到底怎么样促成者说的打印两列打印三列的情况。
第一,讲解之前,先安装几个默认值:

    /**
     * 打印纸一行最大的字节
     */
    private static final int LINE_BYTE_SIZE = 32;

    /**
     * 打印三列时,中间一列的中心线距离打印纸左侧的距离
     */
    private static final int LEFT_LENGTH = 16;

    /**
     * 打印三列时,中间一列的中心线距离打印纸右侧的距离
     */
    private static final int RIGHT_LENGTH = 16;

    /**
     * 打印三列时,第一列汉字最多显示几个文字
     */
    private static final int LEFT_TEXT_MAX_LENGTH = 5;

俺们明白,通用的打印纸都是发固定宽度之。经过大量测试,得出打印纸一行的绝特别字节数是32只字节。那么根据上面的注解,我们好获以下结论:

LEFT_LENGTH + RIGHT_LENGTH = LINE_BYTE_SIZE

立即是无须置疑的。左侧宽度 + 右侧宽度 必须要等于打印纸总宽。
并且以打印三排的当儿,中间一列是如果放在中显示的,所以LEFT_LENGTHRIGHT_LENGTH犹必是总宽32底一半,也即是得是16.

那如何算某个文字所占的字节数呢?

    /**
     * 获取数据长度
     *
     * @param msg
     * @return
     */
    @SuppressLint("NewApi")
    private static int getBytesLength(String msg) {
        return msg.getBytes(Charset.forName("GB2312")).length;
    }

OK,准备了这样多,海参终于准备好了。接下来就是可准备炒面了~

1、初始化打印机

ASCII 十进制 十六进制
ESC @ 27 64 1B 40

说明:清除打印缓冲区,删除用户从定义字符,打印模式被设为上电时的默认值模式。

代码:

//重置打印机
- (void)resetPrinter {
   Byte reset[] = {0x1B,0x40};
   [self.printData appendBytes:reset length:1];
}

留意:经笔者测试发现,使用初始化命令,之后的等同漫漫命令可能会见失效,目前匪找到原因,可能是打印机问题。另外,由于斯命令会败缓冲区,频繁调用可能会见导致数据丢失,因此尽量少用者命令。

打印两排列

    /**
     * 打印两列
     *
     * @param leftText  左侧文字
     * @param rightText 右侧文字
     * @return
     */
    @SuppressLint("NewApi")
    public static String printTwoData(String leftText, String rightText) {
        StringBuilder sb = new StringBuilder();
        int leftTextLength = getBytesLength(leftText);
        int rightTextLength = getBytesLength(rightText);
        sb.append(leftText);

        // 计算两侧文字中间的空格
        int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;

        for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
            sb.append(" ");
        }
        sb.append(rightText);
        return sb.toString();
    }

那位说话了:“你及时代码明明也是手动拼的空格啊,完美个毛啊!”。大兄弟你消除消气,这里是透过逻辑进行拼接的空格,不是无脑的拼凑。打印两排列的步调如下:

  • 东拼西凑左侧一排的仿
  • 东拼西凑两侧文字中的空格
  • 东拼西凑右侧一排的文字

关键步骤是算两侧文字中的空格。怎么计算为?很粗略,总宽度 - 左侧文字长度 - 右侧文字长度
就是空格的尺寸。

2、打印并换行

ASCII 十进制 十六进制
LF 10 0A

说明:将打印缓冲区中之多少打印出,并且按照目前行间距,把打印纸向前推动一行。

代码:

//打印机并换行
- (void)printAndNewline {
   Byte next[] = {0x0A};
   [self.printData appendBytes:next length:1];
}

打印三排

    /**
     * 打印三列
     *
     * @param leftText   左侧文字
     * @param middleText 中间文字
     * @param rightText  右侧文字
     * @return
     */
    @SuppressLint("NewApi")
    public static String printThreeData(String leftText, String middleText, String rightText) {
        StringBuilder sb = new StringBuilder();
        // 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
        if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
            leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
        }
        int leftTextLength = getBytesLength(leftText);
        int middleTextLength = getBytesLength(middleText);
        int rightTextLength = getBytesLength(rightText);

        sb.append(leftText);
        // 计算左侧文字和中间文字的空格长度
        int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;

        for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
            sb.append(" ");
        }
        sb.append(middleText);

        // 计算右侧文字和中间文字的空格长度
        int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;

        for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
            sb.append(" ");
        }

        // 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
        sb.delete(sb.length() - 1, sb.length()).append(rightText);
        return sb.toString();
    }

打印三列的步子如下:

  • 东拼西凑左侧一排列的仿
  • 东拼西凑左侧文字及高中级文字中的空格
  • 东拼西凑中间的文字
  • 东拼西凑右侧文字及中路文字中的空格
  • 东拼西凑右侧一排列的契

当测算空格的上,为了保险中一列始终维持中心线对一起,所以于盘算中文字长度上,都除以2。

3、打印并走n点行纸

ASCII 十进制 十六进制
LESC J n 27 74 n 1B 4A n

说明:打印缓冲区数据并走纸[ n × 纵向或者横向移动单位] 英寸。0 ≤n ≤
255。最要命移动纸距离是956
mm(不同品牌打印机数值不同)。如果盖这个距离,取最好深距。

代码:

//打印缓冲区数据,并往前走纸n点行
- (void)printAndGoNPointLine:(int)n {
   Byte line[] = {0x1B, 0x4A, n};
   [self.printData appendBytes:line length:3];
}

小心:这里是倒纸点行数,要同字符行数分别

完全打印代码

PrintUtils.selectCommand(PrintUtils.RESET);
PrintUtils.selectCommand(PrintUtils.LINE_SPACING_DEFAULT);
PrintUtils.selectCommand(PrintUtils.ALIGN_CENTER);
PrintUtils.printText("美食餐厅\n\n");
PrintUtils.selectCommand(PrintUtils.DOUBLE_HEIGHT_WIDTH);
PrintUtils.printText("桌号:1号桌\n\n");
PrintUtils.selectCommand(PrintUtils.NORMAL);
PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText(PrintUtils.printTwoData("订单编号", "201507161515\n"));
PrintUtils.printText(PrintUtils.printTwoData("点菜时间", "2016-02-16 10:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("上菜时间", "2016-02-16 11:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("人数:2人", "收银员:张三\n"));

PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD);
PrintUtils.printText(PrintUtils.printThreeData("项目", "数量", "金额\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD_CANCEL);
PrintUtils.printText(PrintUtils.printThreeData("面", "1", "0.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("米饭", "1", "6.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("铁板烧", "1", "26.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("一个测试", "1", "226.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("牛肉面啊啊", "1", "2226.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("牛肉面啊啊啊牛肉面啊啊啊", "888", "98886.00\n"));

PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("合计", "53.50\n"));
PrintUtils.printText(PrintUtils.printTwoData("抹零", "3.50\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("应收", "50.00\n"));
PrintUtils.printText("--------------------------------\n");

PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText("备注:不要辣、不要香菜");
PrintUtils.printText("\n\n\n\n\n");

4、打印并倒n行纸

ASCII 十进制 十六进制
ESC d n 27 100 n 1B 64 n

证明:打印缓冲区里的多寡并上走纸n行(字符行)。0 ≤n ≤
255。该令不影响由ESC 2 要么ESC 3设置的行间距。 最充分活动纸距离呢1016
mm,当所设的价过1016 mm时,取最好可怜价值。

代码:

//打印缓冲区数据,并往前走纸n行
- (void)printAndGoNLine:(int)n {
   Byte line[] = {0x1B, 0x64, n};
   [self.printData appendBytes:line length:3];
}

顾:这里是倒纸字符行数,要跟点行数组别。只有设置了行距后,此命令才有效。使用这命令前,要先行运换行指令,否则设置无效

依此类推

必威体育 5

Paste_Image.png

上学了面的打印格式,那么这小票怎么打印?
区别就是是打印三列的早晚,中间一排列是偏右侧了。相信大家该理解答案了。如果生问号,可以吃自家留言。

出于多读者为我留言,要PrintUtils工具类代码,所以自己管代码发布到github上了,大家好活动下载。地址是:https://github.com/heroxuetao/PrintUtils

5、设置默认行距(1/6英寸)

ASCII 十进制 十六进制
ESC 2 27 50 1B 32

说明:选择约3.75mm 行间距。约34个点。

代码:

//设置默认行间距
- (void)printDefaultLineSpace {
   Byte defaultLineSpace[] = {0x1B,0x32};
   [self.printData appendBytes:defaultLineSpace length:2];
}
要生辅助到您,可以随手来个star 。万分谢谢!

本文止。

6、设置行间距为n 点行

ASCII 十进制 十六进制
ESC 3 n 27 51 n 1B 33 n

说明:设置行间距为[ n × 纵向或者横向移动单位] 英寸。

代码:

//设置行间距为n个点
- (void)printLineSpace:(int)n {
   Byte lineSpace[] = {0x1B,0x33,n};
   [self.printData appendBytes:lineSpace length:3];
}

顾:使用这个命令前,要先期采取换行指令,否则设置无效

7、设置字符右间距

ASCII 十进制 十六进制
ESC SP n 27 32 n 1B 20 n

证:设置字符的下手间距为[n×横于活动单位或纵向移动单位]英寸。0 ≤ n
≤255。最老右间距是31.91毫米(255/203
英寸)。任何超过这价的装都活动转换为最特别右间距。

代码:

//字符右间距
- (void)printCharRightSpace:(int)n {
   Byte line[] = {0x1B, 0x20, n};
   [self.printData appendBytes:line length:3];
}

注意:此命令对汉字不行

8、设置输出对齐方式

ASCII 十进制 十六进制
ESC a n 27 97 n 1B 61 n

征:n = 0或48 为左对伙同;n = 1或49也中等对合;n = 2或50各右对同。

代码:

//设置对齐方式
- (void)setAlignment:(MNAlignmentType)alignmentType {
   Byte align[] = {0x1B,0x61,alignmentType};
   [self.printData appendBytes:align length:3];
}

9、设置字体大小

ASCII 十进制 十六进制
GS ! n 29 33 n 1D 21 n

征:用0 到2 员选择字符高度,4 到7 位选择字符宽度。

必威体育 6

代码:

//字符放大倍数
typedef enum: UInt8 {
   MNPrintFont_1 = 0x00,
   MNPrintFont_2 = 0x11,
   MNPrintFont_3 = 0x22,
   MNPrintFont_4 = 0x33,
   MNPrintFont_5 = 0x44,
   MNPrintFont_6 = 0x55,
   MNPrintFont_7 = 0x66,
   MNPrintFont_8 = 0x77,
} MNPrintFont;


//设置字体大小
-(void)printCharSize:(MNPrintFont)printFont {
   Byte font[] = {0x1D,0x21,printFont};
   [self.printData appendBytes:font length:3];
};

10、选择切纸模式及切纸

ASCII 十进制 十六进制
GS V m 29 86 m 1D 56 m
ASCII 十进制 十六进制
GS V m n 29 86 m n 1D 56 m n

说明:

  • m=0,1,49 ;0 表示全切, 1表示半切,当打印机没有半切功能时,全切;
  • m=66, 0≤n≤255 ;当m=66常常, n代表走纸到(约18mm)+[n*0.125mm]
    位置切纸

代码:

//切纸模式
typedef enum :UInt8 {
   MNCutPaperModelFull = 0x00,
   MNCutPaperModelHalf = 0x01,
   MNCutPaperModelFeedPaperHalf = 0x66
}MNCutPaperModel;

- (void)printCutPaper:(MNCutPaperModel)model Num:(int)n {
   if (model == MNCutPaperModelFull) {
       Byte cut[] = {0x1D, 0x56, model, n};
       [self.printData appendBytes:cut length:4];
   } else {
       Byte cut[] = {0x1D, 0x56, model};
       [self.printData appendBytes:cut length:3];
   }
}

注意及时长长的指令需要打印机支持切纸

10、产生必威体育钱箱脉冲(开钱箱)

ASCII 十进制 十六进制
ESC p m t1 t2 27 112 m t1 t2 1B 70 m t1 t2

说明:

  • m = 0, 1, 48, 49 ; 0 ≤ t1 ≤ 255, 0 ≤ t2 ≤ 255 ;
  • 输出由t1和t2设定的钱箱被脉冲到由m指定的引脚:
M 十进制
0, 48 钱箱插座的引脚 2
1, 49 钱箱插座的引脚 5

代码:

//产生钱箱控制脉冲,一般一个打印机连接一个钱箱,这里默认写死了
-(void)printOpenCashDrawer {
    Byte open[] = {0x1B, 0x70, 0x00, 0x80, 0xFF};
    [self.printData appendBytes:open length:5];
}

瞩目及时漫长指令需要打印机连接钱箱

打印内容

证实:这里而用打印内容通过kCFStringEncodingGB_18030_2000编码,然后发送给打印机

代码:

- (void)printWithContent:(NSString *)content {
    NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);
    NSData *data = [content dataUsingEncoding:enc];
    [self.printData appendData:data];
}

以上仅是有些命,可依据需求,参考指令集文档再做相应的长。这里而取一下的凡,小票打印多用来订单详情类信息,为了是排版更漂亮,这里用的较多之是制约表符/t来而各级一样列对一头,可以直接这样用[self.printManager printWithContent:@"\t"];

图片打印

有关图片打印,这里介绍两栽打印指令:

必威体育 7

各队图模式.png

必威体育 8

一味栅位图.png

原理

因为小票打印机多呢热敏打印机,或针式打印机,且颜色只有黑白两质地。因此,要打印图片,首先使赢得图片的像素数量,然后以图纸展开是非二值化处理,之后拼接打印数据,黑色呢打印的触及,白色为未打印的触发。如此逐行打印图片数。

调动分辨率

-(UIImage*)scaleImageWithImage:(UIImage*)image width:(NSInteger)width height:(NSInteger)height
{
    CGSize size;
    size.width = width;
    size.height = height;
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, width, height)];
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

抱像从数据

  • 博图像像素可以参照Getting the pixel data from a CGImage
    object;

-(CGContextRef)CreateARGBBitmapContextWithCGImageRef:(CGImageRef)inImage
{
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;

    // Get image width, height. We'll use the entire image.
    size_t pixelsWide = CGImageGetWidth(inImage);
    size_t pixelsHigh = CGImageGetHeight(inImage);

    // Declare the number of bytes per row. Each pixel in the bitmap in this
    // example is represented by 4 bytes; 8 bits each of red, green, blue, and
    // alpha.
    bitmapBytesPerRow   = (int)(pixelsWide * 4);
    bitmapByteCount     = (int)(bitmapBytesPerRow * pixelsHigh);

    // Use the generic RGB color space.
    colorSpace =CGColorSpaceCreateDeviceRGB();
    if (colorSpace == NULL)
    {
        return NULL;
    }

    // Allocate memory for image data. This is the destination in memory
    // where any drawing to the bitmap context will be rendered.
    bitmapData = malloc( bitmapByteCount );
    if (bitmapData == NULL)
    {
        CGColorSpaceRelease( colorSpace );
        return NULL;
    }

    // Create the bitmap context. We want pre-multiplied ARGB, 8-bits
    // per component. Regardless of what the source image format is
    // (CMYK, Grayscale, and so on) it will be converted over to the format
    // specified here by CGBitmapContextCreate.
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,      // bits per component
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst);
    if (context == NULL)
    {
        free (bitmapData);
    }

    // Make sure and release colorspace before returning
    CGColorSpaceRelease( colorSpace );

    return context;
}

位图模式指令

  • 根据像素信息以图片展开黑白化处理,并逐行拼接打印信息

- (NSData *) imageToThermalData:(UIImage*)image {
    CGImageRef imageRef = image.CGImage;
    // Create a bitmap context to draw the uiimage into
    CGContextRef context = [self CreateARGBBitmapContextWithCGImageRef:imageRef];
    if(!context) {
        return NULL;
    }

    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);

    CGRect rect = CGRectMake(0, 0, width, height);

    // Draw image into the context to get the raw image data
    CGContextDrawImage(context, rect, imageRef);

    // Get a pointer to the data
    uint32_t *bitmapData = (uint32_t *)CGBitmapContextGetData(context);

    if(bitmapData) {

        uint8_t *m_imageData = (uint8_t *) malloc(width * height/8 + 8*height/8);
        memset(m_imageData, 0, width * height/8 + 8*height/8);
        int result_index = 0;

        for(int y = 0; (y + 24) < height;) {
            m_imageData[result_index++] = 27;
            m_imageData[result_index++] = 51;
            m_imageData[result_index++] = 0;

            m_imageData[result_index++] = 27;
            m_imageData[result_index++] = 42;
            m_imageData[result_index++] = 33;

            m_imageData[result_index++] = width%256;
            m_imageData[result_index++] = width/256;
            for(int x = 0; x < width; x++) {
                int value = 0;
                for (int temp_y = 0 ; temp_y < 8; ++temp_y)
                {
                    uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x];
                    uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];

                    if (gray < 127)
                    {
                        value += 1<<(7-temp_y)&255;
                    }
                }
                m_imageData[result_index++] = value;

                value = 0;
                for (int temp_y = 8 ; temp_y < 16; ++temp_y)
                {
                    uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x];
                    uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];

                    if (gray < 127)
                    {
                        value += 1<<(7-temp_y%8)&255;
                    }

                }
                m_imageData[result_index++] = value;

                value = 0;
                for (int temp_y = 16 ; temp_y < 24; ++temp_y)
                {
                    uint8_t *rgbaPixel = (uint8_t *) &bitmapData[(y+temp_y) * width + x];
                    uint32_t gray = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];

                    if (gray < 127)
                    {
                        value += 1<<(7-temp_y%8)&255;
                    }

                }
                m_imageData[result_index++] = value;
            }
            m_imageData[result_index++] = 13;
            m_imageData[result_index++] = 10;
            y += 24;
        }
        NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
        [data appendBytes:m_imageData length:result_index];
        free(bitmapData);
        return data;

    } else {
        NSLog(@"Error getting bitmap pixel data\n");
    }

    CGContextRelease(context);

    return nil ;
}

光栅位图指令

#pragma mark ********************另一种打印图片的方式****************************
typedef struct ARGBPixel {

    u_int8_t             red;
    u_int8_t             green;
    u_int8_t             blue;
    u_int8_t             alpha;

} ARGBPixel ;

#pragma mark 获取打印图片数据
-(NSData *)getDataForPrintWith:(UIImage *)image{

    CGImageRef cgImage = [image CGImage];

    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);


    NSData* bitmapData = [self getBitmapImageDataWith:cgImage];

    const char * bytes = bitmapData.bytes;

    NSMutableData * data = [[NSMutableData alloc] init];

    //横向点数计算需要除以8
    NSInteger w8 = width / 8;
    //如果有余数,点数+1
    NSInteger remain8 = width % 8;
    if (remain8 > 0) {
        w8 = w8 + 1;
    }
    /**
     根据公式计算出 打印指令需要的参数
     指令:十六进制码 1D 76 30 m xL xH yL yH d1...dk
     m为模式,如果是58毫秒打印机,m=1即可
     xL 为宽度/256的余数,由于横向点数计算为像素数/8,因此需要 xL = width/(8*256)
     xH 为宽度/256的整数
     yL 为高度/256的余数
     yH 为高度/256的整数
     **/
    NSInteger xL = w8 % 256;
    NSInteger xH = width / (88 * 256);
    NSInteger yL = height % 256;
    NSInteger yH = height / 256;

    Byte cmd[] = {0x1d,0x76,0x30,0,xL,xH,yL,yH};


    [data appendBytes:cmd length:8];

    for (int h = 0; h < height; h++) {
        for (int w = 0; w < w8; w++) {
            u_int8_t n = 0;
            for (int i=0; i<8; i++) {
                int x = i + w * 8;
                u_int8_t ch;
                if (x < width) {
                    int pindex = h * (int)width + x;
                    ch = bytes[pindex];
                }
                else{
                    ch = 0x00;
                }
                n = n << 1;
                n = n | ch;
            }
            [data appendBytes:&n length:1];
        }
    }
    return data;
}
#pragma mark 获取图片点阵图数据
-(NSData *)getBitmapImageDataWith:(CGImageRef)cgImage{

    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);

    NSInteger psize = sizeof(ARGBPixel);

    ARGBPixel * pixels = malloc(width * height * psize);

    NSMutableData* data = [[NSMutableData alloc] init];

    [self ManipulateImagePixelDataWithCGImageRef:cgImage imageData:pixels];

    for (int h = 0; h < height; h++) {
        for (int w = 0; w < width; w++) {

            int pIndex = (w + (h * (u_int32_t)width));
            ARGBPixel pixel = pixels[pIndex];

            if ((0.3*pixel.red + 0.59*pixel.green + 0.11*pixel.blue) <= 127) {
                //打印黑
                u_int8_t ch = 0x01;
                [data appendBytes:&ch length:1];
            }
            else{
                //打印白
                u_int8_t ch = 0x00;
                [data appendBytes:&ch length:1];
            }
        }
    }

    return data;
}

// 获取像素信息
-(void)ManipulateImagePixelDataWithCGImageRef:(CGImageRef)inImage imageData:(void*)oimageData
{
    // Create the bitmap context
    CGContextRef cgctx = [self CreateARGBBitmapContextWithCGImageRef:inImage];
    if (cgctx == NULL)
    {
        // error creating context
        return;
    }

    // Get image width, height. We'll use the entire image.
    size_t w = CGImageGetWidth(inImage);
    size_t h = CGImageGetHeight(inImage);
    CGRect rect = {{0,0},{w,h}};

    // Draw the image to the bitmap context. Once we draw, the memory
    // allocated for the context for rendering will then contain the
    // raw image data in the specified color space.
    CGContextDrawImage(cgctx, rect, inImage);

    // Now we can get a pointer to the image data associated with the bitmap
    // context.
    void *data = CGBitmapContextGetData(cgctx);
    if (data != NULL)
    {
        CGContextRelease(cgctx);
        memcpy(oimageData, data, w * h * sizeof(u_int8_t) * 4);
        free(data);
        return;
    }

    // When finished, release the context
    CGContextRelease(cgctx);
    // Free image data memory for the context
    if (data)
    {
        free(data);
    }

    return;
}

东拼西凑图片数,准备发放打印机

//打印图片
- (void)printWithImage:(UIImage *)image width:(float)width height:(float)height {
    UIImage * printImage = [self scaleImageWithImage:image width:width height:height];
    NSData *data = [self imageToThermalData:printImage];
    [self.printData appendData:data];
}

加强图片打印速度

由于打印图片是根据像素点来逐行打印,因此数据量会远超出普通文字,这就招致了打印图片的快慢回比文字慢,尤其是蓝牙打印机。解决方式可以从少单方面入手,1、增加每次发送的数据量(主要针对蓝牙打印机);2、减少图片的数据量。

充实每次发送的数据量(主要针对蓝牙打印机);

有关这或多或少,在产一致首讲话到蓝牙时为会说到,由于蓝牙硬件限制,每次吃打印机发送的数据量是发限定的,因此只要将打印数据拆分,循环发送,代码如下:

- (void)printLongData:(NSData *)printContent{
    NSUInteger cellMin;
    NSUInteger cellLen;
    //数据长度
    NSUInteger strLength = [printContent length];
    if (strLength < 1) {
        return;
    }
    //MAX_CHARACTERISTIC_VALUE_SIZE = 120
    NSUInteger cellCount = (strLength % MAX_CHARACTERISTIC_VALUE_SIZE) ? (strLength/MAX_CHARACTERISTIC_VALUE_SIZE + 1):(strLength/MAX_CHARACTERISTIC_VALUE_SIZE);
    for (int i = 0; i < cellCount; i++) {
        cellMin = i*MAX_CHARACTERISTIC_VALUE_SIZE;
        if (cellMin + MAX_CHARACTERISTIC_VALUE_SIZE > strLength) {
            cellLen = strLength-cellMin;
        }
        else {
            cellLen = MAX_CHARACTERISTIC_VALUE_SIZE;
        }
        NSRange rang = NSMakeRange(cellMin, cellLen);
        //        截取打印数据
        NSData *subData = [printContent subdataWithRange:rang];
        //循环写入数据
        [self.peripheral writeValue:subData forCharacteristic:self.characteristicInfo type:CBCharacteristicWriteWithResponse];
    }
}

这里的MAX_CHARACTERISTIC_VALUE_SIZE大凡独宏定义,表示每次发送的数据长度,经笔者测试,当MAX_CHARACTERISTIC_VALUE_SIZE = 20经常,打印文字是正规进度。但打印图片的快慢挺慢,相应以硬件允许的界定外,每次发尽量多之多少。差品牌型号的打印机,这个参数是见仁见智的,笔者的蓝牙打印机该值最多至140。超出后会现出无法打印问题。末笔者将欠值定为MAX_CHARACTERISTIC_VALUE_SIZE = 120,测试了商店几乎高打印机都没有问题。

此外iOS9以后增加了艺术maximumWriteValueLengthForType:可获取写副特诊的顶酷写副数据量,但经笔者测试,对于部分打印机(比如我们企业的)是匪精确的,因此,不要太依仗之办法,最好还是好沾一个适宜的价值。

减少图片的数据量

如缩减图片的数据量,我们可降低分辨率。通过研究指令集笔者发现,光栅位图的倍宽,横向分辨率降低了千篇一律加倍。倍大,纵向分辨率降低了扳平倍。因此,笔者尝试挑选倍宽、倍高模式,即m=3;此时发觉打印出底图片尺寸比图片要那个一倍增。这样咱们设将图纸的有余、高分别除以2。

按照我们如果打印宽、高哉250之图。m = 3 时,打印命令改吗:

Byte cmd[] = {0x1d,0x76,0x30,3,xL,xH,yL,yH};

调用时:

[self.printManager printWithImage:[UIImage imageNamed:@"1513654780"] width:250/2 height:250/2];

经笔者测试,倍宽、倍高模式打印机图片的快慢,和打印文字速度差不多。但图的清晰度会具有下降。究竟以啊种,可机关权衡。

动用举例

演示代码

此处只是略的授课举例,代码并没有很好之卷入,我们得依据自己的要求,封装一个契合自己之模板类。

    self.printManager.printData.length = 0;
//    [self.printManager resetPrinter];

//    [self.printManager printLineSpace:50];
    [self.printManager printCharSize:MNPrintFont_2];
    [self.printManager setAlignment:MNAlignmentTypeCenter];
    [self.printManager printCharRightSpace:1];
    [self.printManager printWithContent:@"这是标题"];

    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:60];

    [self.printManager setAlignment:MNAlignmentTypeLeft];
    [self.printManager printCharSize:MNPrintFont_1];
    [self.printManager printWithContent:@"商品名称"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"数量"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"价格"];

    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:34];

    [self.printManager printWithContent:@"商品1"];
    [self.printManager printWithContent:@"\t\t"];
    [self.printManager printWithContent:@"2"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"1999"];

    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:25];

    [self.printManager printWithContent:@"商品2"];
    [self.printManager printWithContent:@"\t\t"];
    [self.printManager printWithContent:@"200"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"19"];

    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:1];
    [self.printManager printAndGoNPointLine:25];

    [self.printManager printWithContent:@"商品3"];
    [self.printManager printWithContent:@"\t\t"];
    [self.printManager printWithContent:@"200"];
    [self.printManager printWithContent:@"\t"];
    [self.printManager printWithContent:@"19"];

    [self.printManager printAndNewline];
//    [self.printManager printAndGoNLine:2];
    [self.printManager printAndGoNPointLine:25];

    [self.printManager setAlignment:MNAlignmentTypeCenter];
    [self.printManager printWithContent:@"-----------------------------"];

    [self.printManager printAndNewline];
    //    [self.printManager printAndGoNLine:2];
    [self.printManager printAndGoNPointLine:25];

    [self.printManager setAlignment:MNAlignmentTypeRight];
    [self.printManager printWithContent:@"总计:11598元"];

    [self.printManager printAndNewline];
    [self.printManager printAndGoNPointLine:100];

    [self.printManager setAlignment:MNAlignmentTypeCenter];
    [self.printManager printWithImage:[UIImage imageNamed:@"1513654780"] width:200 height:200];

    [self.printManager printAndNewline];
    [self.printManager printAndGoNPointLine:150];

效果图

必威体育 9

些微票功能图.jpeg

总结

字数所限,这同样篇先介绍通过ESC/POS打印命令集,拼接打印指令,排版打印格式。接下来的文章会介绍如何通过蓝牙或Socket将我们编辑的打印数据发送给打印机。

相关文章