二維碼終于火了,現在大街小巷大小商品廣告上的二維碼標簽都隨處可見,而且大都不是簡單的純二維碼,而是中間有個性圖標的二維碼。
我之前做了一個使用google開源項目zxing實現二維碼、一維碼編碼解碼的程序并開放了源碼(用C#實現的條形碼和二維碼編碼解碼器),今天繼續在此程序基礎上,實現二維碼中間加小圖片。
背景知識
QRcode使用里德-所羅門碼來進行錯誤修正。對于我們來說,里德-所羅門編碼有兩個非常重要的特性。第一,它是一種顯式系統碼,也就是說,你可以在最終的編碼中直接看到原有的信息。就好比我們對”hello world”進行編碼,最終看到的是”hello world”以及其后面跟隨的幾個容錯碼。第二點,里德-所羅門編碼是可以被”異或”的,將兩個不同里德-所羅門編碼得到的結果異或運算后會得到一個新的里德-所羅門碼,并且這個新碼的原碼即是原來兩個原碼的異或。如果你想知道為什么這兩個特性會成立,請看Finite Field Arithmetic and Reed-Solomon Coding.
QRcode
一副QRcode圖像會定義一些獨特的描述符來幫助人們或者電腦識別出自己是一張QRcode。這種描述符隨著QRcode的大小不同而略有區別——越大的QRcode圖像擁有越多的描述符。但是對于人的識別來說,特征最明顯的還是圖片的四個角的符號是固定的,看到這樣的四個角人類就本能的反應:這是一個QRcode。
(實際上,我們可以通過讀取圖像最左上角的兩個象素點來判斷編碼的冗余程度。定義黑色為0,白色為1,那么如果看到00則是L級別的冗余,01是M,10是Q,11則是最高的H級別冗余。
有了上面的這些工作,我們可以非常容易的知道原碼信息在圖像中的位置。然后通過改變自己的原碼信息,就可以改變圖像中的像素以至于可以在里面作圖了。雖說如此,下面的一些情形可以讓事情變得更有趣。
我做的二維碼插入圖片:
需要用到ZXing.Net庫。
ZXing.Net 源代碼地址:http://zxingnet.codeplex.com/
也可以使用Nuget包管理,添加如圖:
之前我給大家免費提供了使用zxing開源項目改造而成的一二維碼編碼解碼器,但未能插入圖片。這次經過一番努力,成功將圖片插入二維碼,并能編碼和解碼。
插入圖片的關鍵在于二維碼容錯系數的調整。
界面:
程序界面如下:
其中WinForm項目是我的Demo程序,zxing是Google的一個開源二維碼項目。
生成二維碼的代碼:
//構造二維碼寫碼器 MultiFormatWriter mutiWriter = new com.google.zxing.MultiFormatWriter(); Hashtable hint=new Hashtable(); hint.Add(EncodeHintType.CHARACTER_SET,"UTF-8"); hint.Add(EncodeHintType.ERROR_CORRECTION,com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.H); //生成二維碼 ByteMatrix bm = mutiWriter.encode(txtMsg.Text, com.google.zxing.BarcodeFormat.QR_CODE, 300, 300,hint); Bitmap img = bm.ToBitmap(); //要插入到二維碼中的圖片 Image middlImg = QRMiddleImg.Image; //獲取二維碼實際尺寸(去掉二維碼兩邊空白后的實際尺寸) System.Drawing.Size realSize = mutiWriter.GetEncodeSize(txtMsg.Text, com.google.zxing.BarcodeFormat.QR_CODE, 300, 300); //計算插入圖片的大小和位置 int middleImgW = Math.Min((int)(realSize.Width / 3.5), middlImg.Width); int middleImgH = Math.Min((int)(realSize.Height / 3.5),middlImg.Height); int middleImgL = (img.Width - middleImgW) / 2; int middleImgT = (img.Height - middleImgH) / 2; //將img轉換成bmp格式,否則后面無法創建 Graphics對象 Bitmap bmpimg = new Bitmap(img.Width, img.Height,System.Drawing.Imaging.PixelFormat.Format32bppArgb); using (Graphics g = Graphics.FromImage(bmpimg)) { g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; g.DrawImage(img, 0, 0); } //在二維碼中插入圖片 System.Drawing.Graphics MyGraphic = System.Drawing.Graphics.FromImage(bmpimg); //白底 MyGraphic.FillRectangle(Brushes.White,middleImgL, middleImgT, middleImgW, middleImgH); MyGraphic.DrawImage(middlImg, middleImgL, middleImgT, middleImgW, middleImgH); pictureBox1.Image = bmpimg; //自動保存圖片到當前目錄 string filename = System.Environment.CurrentDirectory + "\\QR" + DateTime.Now.Ticks.ToString() + ".jpg"; bmpimg.Save(filename, System.Drawing.Imaging.ImageFormat.Jpeg); lbshow.Text = "圖片已保存到:" + filename;
解析二維碼的代碼:
//構建解碼器 MultiFormatReader mutiReader = new com.google.zxing.MultiFormatReader(); Bitmap img = (Bitmap)Bitmap.FromFile(opFilePath); if (img == null) return; LuminanceSource ls = new RGBLuminanceSource(img, img.Width, img.Height); BinaryBitmap bb = new BinaryBitmap(new com.google.zxing.common.HybridBinarizer(ls)); //注意 必須是Utf-8編碼 Hashtable hints = new Hashtable(); hints.Add(EncodeHintType.CHARACTER_SET, "UTF-8"); Result r = mutiReader.decode(bb, hints); txtmsg2.Text = r.Text; lbshow.Text = "解碼成功!";
要在二維碼中插入圖片且可以正常解碼,關鍵是要注意以下幾個地方:
1、必須調整二維碼的容錯參數ErrorCorrectionLevel
Hashtable hint=new Hashtable();
hint.Add(EncodeHintType.CHARACTER_SET,"UTF-8");
hint.Add(EncodeHintType.ERROR_CORRECTION,com.google.zxing.qrcode.decoder.ErrorCorrectionLevel.H);
hint是生成二維碼的方法中最后一個參數,這個參數是一個hashtable,這里可以設置二維碼的編碼、容錯系數等。
容錯系數越高,生成的二維碼圖片越復雜,可以容忍二維碼被污垢弄贓,甚至中間可以加一個小圖片,識別也不受影響。
2、第二個要注意的地方是圖片大小
從二維碼的識別原理可以知道,二維碼中原始信息被加密在下圖黑色部分,而紅色部分都是冗余信息,紅色部分都是可以被自己的圖片替換的。
為了插入圖片的完整性,我們選擇在最中間插入,而且長寬建議為整個二維碼的3/7至1/3
//計算插入圖片的大小和位置 int middleImgW = Math.Min((int)(realSize.Width / 3.5), middlImg.Width); int middleImgH = Math.Min((int)(realSize.Height / 3.5),middlImg.Height); int middleImgL = (img.Width - middleImgW) / 2; int middleImgT = (img.Height - middleImgH) / 2;
我們的例子中用的就是2/7的比例。
3、掃描二維碼時的卡頓問題
直接用MultiFormatReader 進行解碼,既可以識別二維碼,也可以識別條形碼,但會出現卡頓現象。如果你的業務需求只需要識別二維碼,請直接使用QRCodeReader
類來解析,字符集采用utf-8,使用Harder模式,并且把可能的解析格式只定義為BarcodeFormat.QR_CODE
,這對于直接二維碼掃描解析無疑是幫助最大的。
Map<DecodeHintType, Object> mHints;
mQrCodeReader = new QRCodeReader();
mHints = new Hashtable<>();
mHints.put(DecodeHintType.CHARACTER_SET, "utf-8");
mHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
mHints.put(DecodeHintType.POSSIBLE_FORMATS, BarcodeFormat.QR_CODE);
源碼下載地址:
文章列表