Qt 中利用 GNU barcode 生成一维条形码
最近有个项目,需要用到条形码。上网找了一圈,网上很少有介绍如何用 C++ 或者 C代码生成条形码的。偶尔有那么一两篇,也是针对某一种条形码给出的。而且一般都是 Code 39 码或者 Code 128 码这种比较简单的条码。
我调研了一番,感觉开源的条码库里面还是GNU barcode 实现的比较全。基本常见的条形码全都支持。不过 GNU barcode 直接在项目里用不是很方便。因为它封装的太简单了。缺少把条码画出来的功能代码。因此,我花了几天时间,对 GNU barcode 进行了二次封装,封装之后方便在 Qt 项目中使用。
关于如何获取和编译 GNU barcode 已经有了一篇博客来介绍:
https://blog.csdn.net/liyuanbhu/article/details/106300298
这里就只写写我的封装代码。
我把相关功能封装到了一个类中:QlyGNUBarcode
这个类使用起来很简单,下面是个简单的样例代码:
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_I25 | BARCODE_NO_CHECKSUM);
bc.setNO_ASCII(false);
bc.setMargin(5);
bc.encodeText("1234567890");
QImage img = bc.painterImage(2, 80);
输出的图片如下:
如果不想要条码下面的文字,代码可以改为:
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_I25 | BARCODE_NO_CHECKSUM);
bc.setNO_ASCII(true);
bc.setMargin(5);
bc.encodeText("1234567890");
QImage img = bc.painterImage(2, 80);
输出的图片如下:
上面代码中 setMargin() 设置的是条码四周的留白。不同的条码要求的最小留白是不同的。不过一般设5-10个像素就够了。
setBarcodeType() 是设置条码的编码类型。 GNU barcode 支持的条码类型还是比较全的。在 v0.99 中支持一下几种。
-
UPC
-
EAN
-
ISBN
-
code 128-B
-
code 128-C
-
code 39
-
interleaved 2 of 5
-
code 128
-
Codabar
-
Plessey
-
MSI
-
code 93
UPC
其中 UPC 要求字符串长度为 6、7、8(UPC-E)或者 11、12 (UPC-A)。并且字符只能是数字。
下面是一个代码片段:
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_UPC);
bc.encodeText("1234567");
bc.encodeText("12345678901");
EAN
EAN 和 UPC 要求差不多,只支持数字。要求数字是 7位或者12位。通常用于商品的编码。
下面是例子:
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_EAN);
bc.encodeText("1234567");
bc.encodeText("123456789012");
ISBN
ISBN 是图书上用的一种编码,除了图书上,其他地方很少用。这里就不介绍了。
CODE 128
code 128 可以编码任意可以打印出来的 ASCII 字符。
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_128);
bc.encodeText("1234567890ABCDEFGH");
bc.setBarcodeType(BARCODE_128B);
bc.encodeText("3200930012ABC");
CODE 39
CODE 39 这种编码支持数字和大写字母,还支持”+“、”-“、”*“ 等少数几个特殊符号。但是这个编码比较浪费空间。比如下面这个码,本身没有几个字符,却要占这么长的地方。
interleaved 2 of 5
交叉25码。只支持数字。带校验的时候只支持奇数位数,如果输入字符是偶数,则编码是前面会加个 0。 如果不想要这个 0 可以把校验去掉。比如下面的例子:
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_I25);
bc.encodeText("123456");
bc.setBarcodeType(BARCODE_I25 | BARCODE_NO_CHECKSUM);
Plessey
支持 0123456789ABCDE 。这15个字符。输出结果中的字母永远是大写。
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_PLS);
bc.encodeText("12345ABCDE");
MSI
MSI 只支持数字。位数不限。
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_MSI);
bc.encodeText("1234567890");
CODE 93
CODE 93 支持10个数字,26个英文字母,空格等共 48 个字符。
QlyGNUBarcode bc;
bc.setBarcodeType(BARCODE_93);
bc.encodeText("1234567890ABCXYZ+-*");
下面是我的代码:
#ifndef QTGNUBARCODE_H
#define QTGNUBARCODE_H
/**
* @file QlyGNUBarcode.h
* @author LiYuan
* @email 18069211@qq.com
* @version v1.0
* @license GPL2.0
* @brief GNU Barcode 库的一个简单封装,用于在 Qt 程序中生成条形码。
* @details 因为用到了 QPainter 和 QImage, 所以需要 QtGui 模块。
* 可用于 Qt4 和 Qt5 代码中。Qt6 还没有测试。
* @copyright 2015-2021
*/
#include <QImage>
#include <QPainter>
#include "barcode.h"
/**
* @brief The QlyGNUBarcode class 对 GNU Barcode 库的一个封装。
*/
class QlyGNUBarcode
{
public:
QlyGNUBarcode();
~QlyGNUBarcode() {if( bc ) Barcode_Delete(bc);}
/**
* @brief encodeText 对文字进行编码。
* @param text
* @return
*/
bool encodeText(const QString & text);
/**
* @brief setBarcodeType 设置条码的类型。条形码类型很多,如果不设置则选择能编码数据的第一种条码类型。
* @param barcode_type BARCODE_EAN 等,具体参照 barcode.h
*/
void setBarcodeType(int barcode_type) {m_barcode_type = barcode_type;}
/**
* @brief setMargin 条码四周留白区域的大小。
* @param whitespace
*/
void setMargin(int whitespace);
/**
* @brief setNO_ASCII 设置是否在条码上/下面显示对应文字
* @param on
*/
void setNO_ASCII(bool on) {m_NO_ASCII = on;}
QImage paintImage(double width_scale = 2, int height = 60, QImage::Format format = QImage::Format_RGB32);
QImage paintImage(QSize size, QImage::Format format = QImage::Format_RGB32);
bool render(QPainter &painter);
bool render(QPainter &painter, QRect rect);
/**
* @brief minSize 返回条码的尺寸,不包括四周的 margin。
* @return
*/
QSize minSize();
/**
* @brief size 返回条码的尺寸,包括四周的 margin。
* @return
*/
QSize size() {return QSize(m_global_width, m_global_height);}
/**
* @brief foregroundColor 返回条码的前景色。
* @return
*/
QColor foregroundColor() const;
void setForegroundColor(const QColor & fgColor);
/**
* @brief backgroundColor 返回条码的背景色。
* @return
*/
QColor backgroundColor() const;
void setBackgroundColor(const QColor & bgColor);
private:
bool updateSizeInfo();
bool drawBar(QPainter &painter);
bool drawText(QPainter &painter);
Barcode_Item * bc;
int m_barcode_type;
bool m_NO_ASCII;
int m_minWidth;
int m_minHeight;
int m_margin;
int m_global_width; // 整体的宽度,包含 margin
int m_global_height; // 整体的高度,包含 margin
QString m_text;
QColor m_fgColor;
QColor m_bgColor;
};
#endif // QTGNUBARCODE_H
/**
* @file QlyGNUBarcode.cpp
* @author LiYuan
* @email 18069211@qq.com
* @version v1.0
* @license GPL2.0
* @brief GNU Barcode 库的一个简单封装,用于在 Qt 程序中生成条形码。
* @details 因为用到了 QPainter 和 QImage, 所以需要 QtGui 模块。
* 可用于 Qt4 和 Qt5 代码中。Qt6 还没有测试。
* @copyright 2015-2021
*/
#include "QlyGNUBarcode.h"
#include <QDebug>
#include <string.h>
QlyGNUBarcode::QlyGNUBarcode()
:bc(nullptr),
m_barcode_type(BARCODE_128),
m_NO_ASCII(false),
m_margin(0),
m_fgColor(Qt::black),
m_bgColor(Qt::white)
{
}
void QlyGNUBarcode::setMargin(int whitespace)
{
m_margin = whitespace;
}
QColor QlyGNUBarcode::foregroundColor() const
{
return m_fgColor;
}
void QlyGNUBarcode::setForegroundColor(const QColor & fgColor)
{
m_fgColor = fgColor;
}
QColor QlyGNUBarcode::backgroundColor() const
{
return m_bgColor;
}
void QlyGNUBarcode::setBackgroundColor(const QColor & bgColor)
{
m_bgColor = bgColor;
}
QSize QlyGNUBarcode::minSize()
{
if( bc ) return QSize();
return QSize(bc->width, bc->height);
}
bool QlyGNUBarcode::encodeText(const QString &text)
{
if( bc ) Barcode_Delete(bc);
bc = Barcode_Create((char *) text.toLocal8Bit().data());
//Barcode_Position(bc, 0, 0, 0, 0, 1.0);
bc->margin = m_margin;
int flags = m_barcode_type;
if(m_NO_ASCII)
{
flags = m_barcode_type | BARCODE_NO_ASCII;
}
else
{
flags = m_barcode_type;
}
bc->flags = flags;
Barcode_Encode(bc, flags);
updateSizeInfo();
return (bc);
}
bool QlyGNUBarcode::updateSizeInfo()
{
if( !bc ) return false;
if ( !bc->partial || !bc->textinfo )
{
bc->error = EINVAL;
return false;
}
/* First, calculate barlen */
int barlen = bc->partial[0] - '0';
for (char * ptr = bc->partial + 1; *ptr; ptr++)
{
if ( isdigit(*ptr) )
{
barlen += (*ptr - '0');
}
else if ( islower(*ptr) )
{
barlen += (*ptr - 'a' + 1);
}
}
m_minWidth = barlen; // 这个宽度是计算出的最小宽度
m_minHeight = 80; // 默认的高度
qDebug() << "m_minWidth = " << m_minWidth;
qDebug() << "m_minHeight = " << m_minHeight;
/* The scale factor depends on bar length */
if ( (fabs(bc->scalef) < 1e-6) )
{
if ( !bc->width )
{
bc->width = barlen; /* default */
}
bc->scalef = (double) bc->width / (double)barlen;
//qDebug() << "fabs(bc->scalef) < 1e-6 , bc->width = " << bc->width << "bc->scalef = " << bc->scalef;
}
/* The width defaults to "just enough" */
if ( !bc->width )
{
bc->width = barlen * bc->scalef + 1;
}
/* But it can be too small, in this case enlarge and center the area */
if (bc->width < barlen * bc->scalef)
{
int wid = barlen * bc->scalef + 1;
bc->xoff -= (wid - bc->width) / 2 ;
bc->width = wid;
/* Can't extend too far on the left */
if (bc->xoff < 0)
{
bc->width += -bc->xoff;
bc->xoff = 0;
}
}
/* The height defaults to 80 points (rescaled) */
if ( !bc->height )
{
bc->height = 80 * bc->scalef;
}
/* If too small (5 + text), reduce the scale factor and center */
int i = 5 + 10 * (( bc->flags & BARCODE_NO_ASCII) == 0 );
if (bc->height < i * bc->scalef )
{
double scaleg = ((double) bc->height) / i;
int wid = bc->width * scaleg / bc->scalef;
bc->xoff += ( bc->width - wid ) / 2;
bc->width = wid;
bc->scalef = scaleg;
}
m_margin = bc->margin;
m_global_width = bc->xoff + bc->width + 2 * bc->margin;
m_global_height = bc->yoff + bc->height + 2 * bc->margin;
return true;
}
bool QlyGNUBarcode::render(QPainter &painter, QRect rect)
{
int xoffset = bc->xoff;
int yoffset = bc->yoff;
int width = bc->width;
int height = bc->height;
double scalef = bc->scalef;
bc->xoff = rect.left();
bc->xoff = rect.top();
bc->width = rect.width() - 2 * m_margin;
bc->height = rect.height() - 2 * m_margin;
bc->scalef = 0.0;
updateSizeInfo();
bool ret = render(painter);
bc->xoff = xoffset;
bc->yoff = yoffset;
bc->width = width;
bc->height = height;
bc->scalef = scalef;
return ret;
}
bool QlyGNUBarcode::drawBar(QPainter &painter)
{
int mode = '-';
int i; /* text below bars */
char * ptr;
double xpos = bc->margin + (bc->partial[0] - '0') * bc->scalef;
for (ptr = bc->partial + 1, i = 1; *ptr; ptr++, i++)
{
/* special cases: '+' and '-' */
if (*ptr == '+' || *ptr == '-')
{
mode = *ptr; /* don't count it */
i++;
continue;
}
/* j is the width of this bar/space */
int j;
if (isdigit (*ptr)) j = *ptr - '0';
else j = *ptr - 'a' + 1;
double x0, y0, yr;
if (i % 2) /* bar */
{
//qDebug() << "bc->xoff = " << bc->xoff << ", xpos = " << xpos;
x0 = bc->xoff + xpos;// + (j * scalef) / 2;
y0 = bc->yoff + bc->margin;
yr = bc->height;
if ( !(bc->flags & BARCODE_NO_ASCII) )
{ /* leave space for text */
if (mode == '-')
{
/* text below bars: 10 points or five points */
//y0 += (isdigit(*ptr) ? 10 : 5) * scalef;
yr -= (isdigit(*ptr) ? 10 : 5) * bc->scalef;
}
else
{ /* '+' */
/* text above bars: 10 or 0 from bottom, and 10 from top */
y0 += (isdigit(*ptr) ? 10 : 0) * bc->scalef;
yr -= (isdigit(*ptr) ? 20 : 10) * bc->scalef;
}
}
painter.fillRect(QRect(x0, y0, (j * bc->scalef), yr ), m_fgColor);
//qDebug() << "fillRect: " << QRect(x0, y0, (j * m_scalef), yr );
}
xpos += j * bc->scalef;
}
return true;
}
bool QlyGNUBarcode::drawText(QPainter &painter)
{
int mode = '-'; /* reinstantiate default */
if (!(bc->flags & BARCODE_NO_ASCII))
{
painter.save();
painter.setPen(m_fgColor);
for (char * ptr = bc->textinfo; ptr; ptr = strchr(ptr, ' '))
{
//qDebug() << "*";
while (*ptr == ' ') ptr++;
if (!*ptr) break;
if (*ptr == '+' || *ptr == '-')
{
mode = *ptr; continue;
}
double f1, f2;
char c;
if (sscanf(ptr, "%lf:%lf:%c", &f1, &f2, &c) != 3)
{
//fprintf(stderr, "barcode: impossible data: %s\n", ptr);
continue;
}
painter.setFont(QFont("Helvetica", (int)(0.8 * f2 * bc->scalef)));
int x_pos = bc->xoff + f1 * bc->scalef + bc->margin;
int y_pos = 0;
if(mode == '-')
{
y_pos = (double)bc->yoff + bc->margin + bc->height ;//- 8 * bc->scalef;
}
else
{
y_pos = (double)bc->yoff + bc->margin;
}
painter.drawText(QPoint(x_pos, y_pos ), QString(QChar(c)));
}
painter.restore();
}
return true;
}
QImage QlyGNUBarcode::paintImage(QSize size, QImage::Format format)
{
if(!bc) return QImage();
int w = size.width();
double h = size.height();
return paintImage(w / bc->width, h, format);
}
QImage QlyGNUBarcode::paintImage(double width_scale, int height, QImage::Format format)
{
if(!bc) return QImage();
int bcWidth = bc->width; // 保存现场
int bcHeight = bc->height;
float bcScalef = bc->scalef;
bc->width = bc->width * width_scale;
bc->scalef = width_scale;
bc->height = height;
//qDebug() << "bc->margin = " << bc->margin;
int w = bc->width + 2 * bc->margin;
int h = bc->height + 2 * bc->margin;
//qDebug() << "in QtGnuBarcode::toImage(), w = " << w << ", h = " << h;
QImage img(w, h, format);
QPainter painter(&img);
img.fill(m_bgColor);
painter.setBrush(m_fgColor);
render(painter);
bc->width = bcWidth; // 恢复原状
bc->height = bcHeight;
bc->scalef = bcScalef;
return img;
}
bool QlyGNUBarcode::render(QPainter &painter)
{
if( !bc ) return false;
int w = bc->width + 2 * bc->margin;
int h = bc->height + 2 * bc->margin;
painter.fillRect(QRect(0, 0, w, h), m_bgColor);
if ( !bc->partial || !bc->textinfo )
{
bc->error = EINVAL;
return false;
}
drawBar(painter);
qDebug() << "bc->flags & BARCODE_NO_ASCII = " <<( bc->flags & BARCODE_NO_ASCII);
if(! (bc->flags & BARCODE_NO_ASCII) )
{
drawText(painter);
}
return true;
}