现在二维码很流行,大街小巷大小商品广告上的二维码标签都随处可见,而且大都不是简单的纯二维码,而是中间有个性图标的二维码。网上比较少介绍基于windows下的C++二维码编码实现的文章,最近正好在windows平台下使用QT开发了一个简单的生成带LOGO的二维码小程序,特记录一下。使用的是Libqrencode开源c代码,这是一个c 语言的QR code 生成库。Libqrencode 暂时只支持 QR Code model 2,如果需要ECI 或者FNC1模式的话,还要想别的办法。
二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的,其应用广泛,如:产品防伪/溯源、广告推送、网站链接、数据下载、商品交易、定位/导航、电子凭证、车辆管理、信息传递、名片交流、wifi共享等。
二维码的信息承载量很大,最大数据含量可达1850个字符,信息内容可包含,字母,数字,汉字,字符,片假名等。二维码常用的码制有:PDF417二维条码,Datamatrix二维条码,QR Code,Code 49,Code 16K,Codeone等。
QRCode是日本人开发的,使用里德-所罗门码来进行错误修正。对于我们来说,里德-所罗门编码有两个非常重要的特性。第一,它是一种显式系统码,也就是说,你可以在最终的编码中直接看到原有的信息。就好比我们对”hello world”进行编码,最终看到的是”hello world”以及其后面跟随的几个容错码。第二点,里德-所罗门编码是可以被”异或”的,将两个不同里德-所罗门编码得到的结果异或运算后会得到一个新的里德-所罗门码,并且这个新码的原码即是原来两个原码的异或。
一副QRcode图像会定义一些独特的描述符来帮助人们或者电脑识别出自己是一张QRcode。这种描述符随着QRcode的大小不同而略有区别——越大的QRcode图像拥有越多的描述符。但是对于人的识别来说,特征最明显的还是图片的四个角的符号是固定的,看到这样的四个角人类就本能的反应:这是一个QRcode。(实际上,我们可以通过读取图像最左上角的两个象素点来判断编码的冗余程度。定义黑色为0,白色为1,那么如果看到00则是L级别的冗余,01是M,10是Q,11则是最高的H级别冗余。)
二维码的容错级分别为:L,M,Q和H。其中,L最低,H最高。如何从二维码中一眼看出其容错级别呢?看下图:
图1 容错级示例
如上图所示,关键部分已用红色框框起来,识别方向也已用箭头标示。假定黑色块为1,白色为0,那么:
两黑色块平列,黑+黑=11,容错级为L;黑+白=10,为M级容错;白+黑=01,为Q级容错;白+白=00,为H级容错。
二维码的特性:
1) 在可识别的情况下,可成比例任意缩放。
2) 可任意角度旋转。
3) 可以水平或垂直反转(镜像)。
4) 在对比分明、在不影响硬件和软件识别的情况下,可彩色化。
5) 在不影响硬件和软件识别的情况下可艺术化。
有了上面的这些工作,我们可以非常容易的知道原码信息在图像中的位置。然后通过改变自己的原码信息,就可以改变图像中的像素以至于可以在里面添加Logo了,我实现的程序就是在二维码的中间添加需要的Logo。
首先下载libqrencode库,由于libqrencode需要使用linpng库,而libpng需要zlib的支持,因此还要下载这两个库,我下载的是这两个库的源码进行编译生成库文件,然后将生成的文件拷贝到我的项目文件夹下,在我程序中用#pragmacomment(lib,"libpng16.lib")和#pragmacomment(lib,"zlib.lib")导入使用,libpng和zlib库的使用参见我的另一篇博文《Visual studio中编译和使用libpng和zlib》—链接地址:http://blog.csdn.net/liuyez123/article/details/50629906.
将libpng和zlib的头文件拷贝到项目文件夹,并将libqrencode库的头文件和C源码拷贝到项目文件夹,将拷入的libqrencode库目录下的qrenc.c文件剪切到项目目录下,并将文件中的mian()函数和getopt()函数删除。
新建一个QT项目,将上面准备的头文件和源码加入到项目中,并设计程序的主界面如下:
单选中添加Logo的Radio Button的时候就可以选择使用的Logo图片。然后在文本输入行中输入相应的信息,点击生成按钮就可以生成所要的二维码,并保存在磁盘目录下。
程序中比较麻烦的地方主要是两个地方:
- 显示图片,如果使用QLabel来显示图片的话,在多显示几次后图片就会出问题,因此专门设计了一个QWidget来显示图片。并实现二维码和Logo的叠加显示和独立显示。
- 将二维码图片和Logo图片进行合成也是一个比较麻烦的地方,使用QT进行图片的合成,但需要计算正确的合成位置以及将Logo图片缩放到相应的比例,在程序中我使用大小为宽高均为二维码的0.24。另外也可以提高二维码的容错率来,但是这样的话保存的信息量就会减少。
工程项目的目录结构如下图:
具体的程序清单如下:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = QRCode TEMPLATE = app DEFINES *= HAVE_CONFIG_H INCLUDEPATH += qrencode-3.4.4/ INCLUDEPATH += lpng-1.6.2.1/ \ zlib-1.2.8/ SOURCES += main.cpp\ qrcodewidget.cpp \ qrenc.c \ qrencode-3.4.4/bitstream.c \ qrencode-3.4.4/mask.c \ qrencode-3.4.4/mmask.c \ qrencode-3.4.4/mqrspec.c \ qrencode-3.4.4/qrencode.c \ qrencode-3.4.4/qrinput.c \ qrencode-3.4.4/qrspec.c \ qrencode-3.4.4/rscode.c \ qrencode-3.4.4/split.c \ qimageviewer.cpp HEADERS += \ qrencode-3.4.4/split.h \ qrencode-3.4.4/rscode.h \ qrencode-3.4.4/qrspec.h \ qrencode-3.4.4/qrinput.h \ qrencode-3.4.4/qrencode_inner.h \ qrencode-3.4.4/qrencode.h \ qrencode-3.4.4/mqrspec.h \ qrencode-3.4.4/mmask.h \ qrencode-3.4.4/mask.h \ qrencode-3.4.4/bitstream.h \ qrcodewidget.h \ lpng-1.6.2.1/png.h \ lpng-1.6.2.1/pngconf.h \ lpng-1.6.2.1/pngdebug.h \ lpng-1.6.2.1/pnginfo.h \ lpng-1.6.2.1/pnglibconf.h \ lpng-1.6.2.1/pngpriv.h \ lpng-1.6.2.1/pngstruct.h \ zlib-1.2.8/crc32.h \ zlib-1.2.8/deflate.h \ zlib-1.2.8/gzguts.h \ zlib-1.2.8/inffast.h \ zlib-1.2.8/inffixed.h \ zlib-1.2.8/inflate.h \ zlib-1.2.8/inftrees.h \ zlib-1.2.8/trees.h \ zlib-1.2.8/zconf.h \ zlib-1.2.8/zlib.h \ zlib-1.2.8/zutil.h \ qrencode-3.4.4/config.h \ qrencode-3.4.4/getopt.h \ qimageviewer.h FORMS += qrcode.ui RESOURCES += \ qrencode.qrc
#include "qrcodewidget.h" #include
int main(int argc, char *argv[]) { QApplication a(argc, argv); QRCodeWidget w; w.show(); return a.exec(); }
#ifndef QIMAGEVIEWER_H #define QIMAGEVIEWER_H #include
#include #include class QImageViewer : public QWidget { Q_OBJECT public: explicit QImageViewer(QWidget *parent = 0); const QPixmap *pixmap() const; public slots: void setPixmap(const QPixmap &pix); protected: void paintEvent(QPaintEvent *); private: QPixmap m_pixmap; }; #endif // QIMAGEVIEWER_H
#ifndef QRCODE_H #define QRCODE_H #include
#include namespace Ui { class QRCode; } class QRCodeWidget : public QWidget { Q_OBJECT public: explicit QRCodeWidget(QWidget *parent = 0); ~QRCodeWidget(); private slots: void on_pushButton_clicked(); void on_radioButton_toggled(bool checked); private: Ui::QRCode *ui; char outfile[128]; double xRate =0.24; double yRate =0.24; QString logoFileName; }; #endif // QRCODE_H
#include "qimageviewer.h" #include
QImageViewer::QImageViewer(QWidget *parent) : QWidget(parent) { } void QImageViewer::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); QStyleOption opt; opt.init(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); if (m_pixmap.isNull()) return; QPainter painter(this); painter.setRenderHint(QPainter::SmoothPixmapTransform); QSize pixSize = m_pixmap.size(); pixSize.scale(event->rect().size(), Qt::KeepAspectRatio); QPoint topleft; topleft.setX((this->width() - pixSize.width()) / 2); topleft.setY((this->height() - pixSize.height()) / 2); painter.drawPixmap(topleft, m_pixmap.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } const QPixmap* QImageViewer::pixmap() const { return &m_pixmap; } void QImageViewer::setPixmap(const QPixmap &pix) { m_pixmap = pix; this->update(); }
#include
#include "qimageviewer.h" #include "qrcodewidget.h" #include "ui_qrcode.h" #include "qrencode.h" #include "qrenc.c" #include #pragma comment(lib,"libpng16.lib") #pragma comment(lib,"zlib.lib") QRCodeWidget::QRCodeWidget(QWidget *parent) : QWidget(parent, Qt::Dialog), ui(new Ui::QRCode) { ui->setupUi(this); strcpy(outfile , "output.png"); QSize logoSize; logoSize.setWidth(ui->imageWidget->width()*xRate); logoSize.setHeight(ui->imageWidget->height()*yRate); ui->logowidget->resize(logoSize); QPoint topleft; topleft.setX(ui->imageWidget->width()*(1-xRate)/2); topleft.setY(ui->imageWidget->height()*(1-yRate)/2); ui->logowidget->move(topleft); ui->logowidget->hide(); ui->logowidget->setWindowFlags(Qt::WindowStaysOnTopHint); ui->logowidget->show(); setWindowTitle(tr("QrCode")); } QRCodeWidget::~QRCodeWidget() { delete ui; } void QRCodeWidget::on_pushButton_clicked() { QString info = ui->lineEdit->text(); if (info.isEmpty()) { return; } margin = 1; ::size = 7; version = 2; qrencode((unsigned char *)info.toStdString().data(), info.length(), outfile); if (!(logoFileName.isNull())&&(logoFileName != "")) { QImage resultImage; QImage qrCodeImage(outfile); QSize resultSize; resultSize.setWidth(qrCodeImage.width()); resultSize.setHeight(qrCodeImage.height()); resultImage = QImage(resultSize, QImage::Format_ARGB32_Premultiplied); QPainter painter(&resultImage); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.fillRect(resultImage.rect(), Qt::transparent); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); QImage image = QImage(); image.load(logoFileName); QSize logoSize; logoSize.setWidth(qrCodeImage.width()*xRate); logoSize.setHeight(qrCodeImage.height()*yRate); QImage newImage = image.scaled(logoSize,Qt::KeepAspectRatio,Qt::SmoothTransformation); QPoint logoPoint((qrCodeImage.width() - newImage.width()) / 2, (qrCodeImage.height() - newImage.height()) / 2); painter.drawImage(logoPoint, newImage); painter.setCompositionMode(QPainter::CompositionMode_DestinationAtop); painter.drawImage(0, 0, qrCodeImage); painter.setCompositionMode(QPainter::CompositionMode_DestinationOver); painter.fillRect(resultImage.rect(), Qt::white); painter.end(); resultImage.save(outfile); // ui->imageWidget->setPixmap(QPixmap::fromImage(resultImage)); } // else ui->imageWidget->setPixmap(QPixmap(outfile)); } void QRCodeWidget::on_radioButton_toggled(bool checked) { if (checked) { logoFileName = QFileDialog::getOpenFileName(this, tr("Open Logo Image")); if (!logoFileName.isEmpty()) ui->logowidget->setPixmap(QPixmap(logoFileName)); else ui->radioButton->setChecked(false); } else { ui->logowidget->setPixmap(QPixmap()); ui->imageWidget->update(); logoFileName = ""; } }
文中使用的代码可以从以下链接下载:
《WIndow下使用QT C++开发生成带Logo的二维码的程序》—链接地址:http://download.csdn.net/detail/liuyez123/9428255