环境:Qt5.3 MSVC2010
编译器:Qt Creator
描述:
最近公司项目卡在了POS机打印小票上,因为涉及打印机太多,很多打印机对ESC/POS指令集支持又不是特别好,所以直接用ESC指令集打不出需要的条形码。唯一的方式只有先生成需要的条形码图片,再解析图片把数据发送给POS小票机打印。
主要问题就是生成条形码算法了。
现在最常见的条形码有两种编码格式,一种是39码(code39),一种是128码(code128)。
最初打算使用的是39码,代码做完之后,发现39码的编码规则虽然简单,但是不适合太长的条形码,一般码数超过8位图片就显得很长,再长一些甚至就会超出POS打印机的最大宽度。相对于39码,128码的编码规则虽然复杂,字符集庞大,但是能把纯数字的条形码长度压缩近一倍(相对于39码),所以后来又重做了128码图片生成。
由于128码支持的字符集太多,但是公司项目目前只针对纯数字的条形码,所以128码的代码只写了极小部分的字符集支持,就不贴上来。
只贴39码的算法。
解析:
关于39码,遵循以下规则(不少资料中会总结为5条规则,但是并不好理解,我总结为一下几条)
1.支持0~9 A~Z(大写字母) + - * / % ¥[空格]其中*只作为起始终止符,不出现在条码中)
2.每个字符对应一个12位的二进制逻辑码(对照表见后)
3.对应逻辑码中,1代表条形码的黑线,0代表条形码白线(不少资料也会约束11为粗黑线,00为粗白线,1为细黑线,0为细白线)
4.条形码的开头与结尾必须使用*做标识符
PS:
第一次做出代码生成图片时,扫描不成功,这个问题纠结了很长时间,后来把合法的条形码和自己生成的条形码挨个对比,才发现问题。
实际上,每个字符对应的条形码之间,还有一个单位的白线。
也就是说,在QString Code39::CodeBinary(QString)中,每转换一个二进制逻辑码后,都要加一个“0”来分割两个字符的二进制逻辑码。这点非常重要!
Code39.cpp(头文件略)
#include "Code39.h"
#include <QDebug>
#include <QDateTime>
#include <QMessageBox>
#include <QFont>
Code39::Code39(QString BarCode,QString SavePath)
{
barcode=BarCode;
path=SavePath;
InitMap();
start(barcode);
}
void Code39::start(QString barcode)
{
//校验字符是否合法
QString com="0123456789ABCDEFGHIJKLMNOPQISTUVWXWZ+-*/%$. ";//合法字符模版
for(int i=0;i<barcode.size();i++)
{
if(!com.contains(barcode.at(i)))
{
QMessageBox msg;
msg.setText(QStringLiteral("条形码字符不合法!\n合法字符:A~Z 0~9 +-/%$[空格]"));
msg.exec();
return;
}
}
draw(CodeBinary(barcode),barcode);
}
void Code39::draw(QString BinaryNum,QString barcode)
{
int MAXWIDTH;
int MAXHEIGHT=100;
int FONTHEIGHT=30;
//设定条码线条宽度px
int LineWidth=3;
//预留10px的左右空白;
MAXWIDTH=LineWidth*BinaryNum.size()+10;
//计算条线宽度时,精度丢失造成条码右边空白,进行右移居中
int move=(MAXWIDTH-LineWidth*BinaryNum.size())/2;
QBitmap bmp(MAXWIDTH,MAXHEIGHT+FONTHEIGHT);
QPainter painter(&bmp);
QPen white,black;
// painter.setRenderHint(QPainter::Antialiasing,true);//弧线圆润
white.setColor(QColor(255,255,255));
black.setColor(QColor(0,0,0));
white.setWidth(LineWidth);
black.setWidth(LineWidth);
//背景充填白色
painter.setPen(white);
painter.drawRect(0,0,MAXWIDTH,MAXHEIGHT+FONTHEIGHT);
//画线
for(int i=0;i<BinaryNum.size();i++)
{
if(BinaryNum.at(i)=="1") painter.setPen(black);
else painter.setPen(white);
painter.drawLine(i*LineWidth+LineWidth/2+move,0,i*LineWidth+LineWidth/2+move,MAXHEIGHT);
}
//添加底部条形码字符
painter.setPen(black);
painter.setRenderHint(QPainter::Antialiasing,true);
painter.setRenderHint(QPainter::TextAntialiasing,true);
painter.setRenderHint(QPainter::HighQualityAntialiasing,true);
QFont font;
font.setFamily("Impact");
font.setPixelSize(20);
font.setWeight(50);
painter.setFont(font);
painter.drawText(10,100,MAXWIDTH,30,Qt::AlignCenter,barcode);
bmp.save(path+QDateTime::currentDateTime().toString("MM-dd-hh-mm-ss-zz")+".bmp");
}
QString Code39::CodeBinary(QString barcode)
{
barcode="*"+barcode+"*";
QString str="";
for(int i=0;i<barcode.size();i++)
{
str+=map[barcode.at(i)]+"0";//每个字符的二进制逻辑码之间,用“0”隔开(加一条白线)
}
return str;
}
void Code39::InitMap()
{
map.insert("A","110101001011");
map.insert("B","101101001011");
map.insert("C","110110100101");
map.insert("D","101011001011");
map.insert("E","110101100101");
map.insert("F","101101100101");
map.insert("G","101010011011");
map.insert("H","110101001101");
map.insert("I","101101001101");
map.insert("J","101011001101");
map.insert("K","110101010011");
map.insert("L","101101010011");
map.insert("M","110110101001");
map.insert("N","101011010011");
map.insert("O","110101101001");
map.insert("P","101101101001");
map.insert("Q","101010110011");
map.insert("R","110101011001");
map.insert("S","101101011001");
map.insert("T","101011011001");
map.insert("U","110010101011");
map.insert("V","100110101011");
map.insert("W","110011010101");
map.insert("X","100101101011");
map.insert("Y","110010110101");
map.insert("Z","100110110101");
map.insert("0","101001101101");
map.insert("1","110100101011");
map.insert("2","101100101011");
map.insert("3","110110010101");
map.insert("4","101001101011");
map.insert("5","110100110101");
map.insert("6","101100110101");
map.insert("7","101001011011");
map.insert("8","110100101101");
map.insert("9","101100101101");
map.insert("+","100101001001");
map.insert("-","100101011011");
map.insert("*","100101101101");
map.insert("/","100100101001");
map.insert("%","101001001001");
map.insert("$","100100100101");
map.insert(".","110010101101");
map.insert(" ","100110101101");
}
对照表
39码的字符编码方式
(一) 英文字母部分
字符 | 逻辑型态 | 字符 | 逻辑型态 |
A | 110101001011 | N | 101011010011 |
B | 101101001011 | O | 110101101001 |
C | 110110100101 | P | 101101101001 |
D | 101011001011 | Q | 101010110011 |
E | 110101100101 | R | 110101011001 |
F | 101101100101 | S | 101101011001 |
G | 101010011011 | T | 101011011001 |
H | 110101001101 | U | 110010101011 |
I | 101101001101 | V | 100110101011 |
J | 101011001101 | W | 110011010101 |
K | 110101010011 | X | 100101101011 |
L | 101101010011 | Y | 110010110101 |
M | 110110101001 | Z | 100110110101 |
(二) 数字与特殊符号部分
字符 | 逻辑型态 | 字符 | 逻辑型态 |
0 | 101001101101 | + | 100101001001 |
1 | 110100101011 | - | 100101011011 |
2 | 101100101011 | * | 100101101101 |
3 | 110110010101 | / | 100100101001 |
4 | 101001101011 | % | 101001001001 |
5 | 110100110101 | $ | 100100100101 |
6 | 101100110101 | . | 110010101101 |
7 | 101001011011 | 空白 | 100110101101 |
8 | 110100101101 |
|
|
9 | 101100101101 |
|
|