WIndow下使用QT C++開發生成帶Logo的二維碼的程式
現在二維碼很流行,大街小巷大小商品廣告上的二維碼標籤都隨處可見,而且大都不是簡單的純二維碼,而是中間有個性圖示的二維碼。網上比較少介紹基於windows下的C++二維碼編碼實現的文章,最近正好在windows平臺下使用QT開發了一個簡單的生成帶LOGO的二維碼小程式,特記錄一下。使用的是Libqrencode開源c程式碼,這是一個c 語言的QR code 生成庫。Libqrencode 暫時只支援 QR Code model 2,如果需要ECI 或者FNC1模式的話,還要想別的辦法。
1. 背景知識
二維條碼/二維碼(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。
2. 實現步驟
2.1. 下載和編譯所需的庫原始碼
首先下載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.
2.2. 匯入相應的標頭檔案及原始碼
將libpng和zlib的標頭檔案拷貝到專案資料夾,並將libqrencode庫的標頭檔案和C原始碼拷貝到專案資料夾,將拷入的libqrencode庫目錄下的qrenc.c檔案剪下到專案目錄下,並將檔案中的mian()函式和getopt()函式刪除。
2.3. 建立QT專案
新建一個QT專案,將上面準備的標頭檔案和原始碼加入到專案中,並設計程式的主介面如下:
單選中新增Logo的Radio Button的時候就可以選擇使用的Logo圖片。然後在文字輸入行中輸入相應的資訊,點選生成按鈕就可以生成所要的二維碼,並儲存在磁碟目錄下。
程式中比較麻煩的地方主要是兩個地方:
- 顯示圖片,如果使用QLabel來顯示圖片的話,在多顯示幾次後圖片就會出問題,因此專門設計了一個QWidget來顯示圖片。並實現二維碼和Logo的疊加顯示和獨立顯示。
- 將二維碼圖片和Logo圖片進行合成也是一個比較麻煩的地方,使用QT進行圖片的合成,但需要計算正確的合成位置以及將Logo圖片縮放到相應的比例,在程式中我使用大小為寬高均為二維碼的0.24。另外也可以提高二維碼的容錯率來,但是這樣的話儲存的資訊量就會減少。
3. 具體程式碼
工程專案的目錄結構如下圖:
具體的程式清單如下:
3.1. QRCode.pro檔案
<pre name="code" class="cpp"><span style="font-size:14px;">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 </span>
3.2. main.cpp檔案
<pre name="code" class="cpp"><span style="font-size:14px;">#include "qrcodewidget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); QRCodeWidget w; w.show(); return a.exec(); }</span>
3.3. qimageviewer.h檔案
<pre name="code" class="cpp"><span style="font-size:14px;">#ifndef QIMAGEVIEWER_H #define QIMAGEVIEWER_H #include <QWidget> #include <QPainter> #include <QPaintEvent> 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</span>
3.4. qrcodewidget.h檔案
<pre name="code" class="cpp"><span style="font-size:14px;">#ifndef QRCODE_H #define QRCODE_H #include <QWidget> #include <QTextCodec> 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</span>
3.5. qimageviewer.cpp檔案
<pre name="code" class="cpp"><span style="font-size:14px;">#include "qimageviewer.h" #include <QStyleOption> 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(); }</span>
3.6. qrcodewidget.cpp檔案
#include <QPicture> #include "qimageviewer.h" #include "qrcodewidget.h" #include "ui_qrcode.h" #include "qrencode.h" #include "qrenc.c" #include <QFileDialog> #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 = ""; } }