Android利用ZXing生成帶LOGO的二維碼圖片
*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布
前言:
昨天有個小伙伴問我怎么動態生成帶logo的二維碼,雖然我也見過帶logo的二維碼,但是不知道具體實現,我猜想ZXing能直接生成帶logo的二維碼吧。於是我去百度了一番,結果都是需要圖片合成的。想了一下,二維碼的主要概念使用來存儲文字信息,並不能存儲圖片信息,而帶logo的二維碼只是提供方加上去讓別人知道這個二維碼的提供方是誰。在百度搜索了一番,搜到的資料或多或少有些瑕疵,而且大多都是互相轉載,內容也沒解釋清楚,也有少部分講解二維碼算法(簡單的生成帶logo的矩形二維碼不涉及二維碼算法),於是自己想寫一篇相關的文章。
一、概述
本文是根據github上一個二維碼庫QRCode中第三個生成二維碼demo改造而來,主要涉及:
Bitmap合成(圖片合成)
二維碼矩陣與一維像素相互轉換(將logo轉成像素放入二維碼中間)
調用系統分享
二、實現
1、通過Bitmap合成logo與白色背景,我們來看一下白色背景圖和logo圖:
咳咳咳~請原諒我盜用郭霖博客上婚紗照頭像,我表示單身狗受到了一萬點暴擊傷害……白色背景圖猶豫全白的無法顯示,於是截了灰色的邊框突出以下。我們來看看合成后的圖:
我們來看下是怎么合成的吧:**
*
* 項目名稱 : ZXingScanQRCode
* 創建人 : skycracks
* 創建時間 : 2016-4-19下午9:53:29
* 版本 : [v1.0]
* 類描述 : LOGO圖片加上白色背景圖片
*/
public class LogoConfig {
/**
*@return 返回帶有白色背景框logo
*/
public Bitmap modifyLogo(Bitmap bgBitmap, Bitmap logoBitmap) {
int bgWidth = bgBitmap.getWidth();
int bgHeigh = bgBitmap.getHeight();
//通過ThumbnailUtils壓縮原圖片,並指定寬高為背景圖的3/4
logoBitmap = ThumbnailUtils.extractThumbnail(logoBitmap,bgWidth*3/4, bgHeigh*3/4, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
Bitmap cvBitmap = Bitmap.createBitmap(bgWidth, bgHeigh, Config.ARGB_8888);
Canvas canvas = new Canvas(cvBitmap);
// 開始合成圖片
canvas.drawBitmap(bgBitmap, 0, 0, null);
canvas.drawBitmap(logoBitmap,(bgWidth - logoBitmap.getWidth()) /2,(bgHeigh - logoBitmap.getHeight()) / 2, null);
canvas.save(Canvas.ALL_SAVE_FLAG);// 保存
canvas.restore();
if(cvBitmap.isRecycled()){
cvBitmap.recycle();
}
return cvBitmap;
}
}
因為我使用的白色背景圖比較小,因此代碼中以白色背景圖得到的bitmap創建畫布Canvas並且縮放logo圖片。可以看到通過ThumbnailUtils.extractThumbnail(logoBitmap,bgWidth*3/4,bgHeigh*3/4, ThumbnailUtils.OPTIONS_RECYCLE_INPUT)將logo寬高縮放為白色背景圖的3/4,我認為做好的比例。圖片合成其實不難,主要通過Canvas 這個類中的方法實現,想要詳細了解的同學請自行百度(以后可能會整理圖片合成寫一篇博文)。
2、二維碼矩陣與一維像素相互轉換(將logo轉成像素放入二維碼中間)
生成帶logo二維碼的方法:/**
* 黑點顏色
*/
private static final int BLACK = 0xFF000000;
/**
* 白色
*/
private static final int WHITE = 0xFFFFFFFF;
/**
* 正方形二維碼寬度
*/
private static final int CODE_WIDTH = 440;
/**
* LOGO寬度值,最大不能大於二維碼20%寬度值,大於可能會導致二維碼信息失效
*/
private static final int LOGO_WIDTH_MAX = CODE_WIDTH / 5;
/**
*LOGO寬度值,最小不能小於二維碼10%寬度值,小於影響Logo與二維碼的整體搭配
*/
private static final int LOGO_WIDTH_MIN = CODE_WIDTH / 10;
/**
* 生成帶LOGO的二維碼
*/
public Bitmap createCode(String content, Bitmap logoBitmap)
throws WriterException {
int logoWidth = logoBitmap.getWidth();
int logoHeight = logoBitmap.getHeight();
int logoHaleWidth = logoWidth >= CODE_WIDTH ? LOGO_WIDTH_MIN
: LOGO_WIDTH_MAX;
int logoHaleHeight = logoHeight >= CODE_WIDTH ? LOGO_WIDTH_MIN
: LOGO_WIDTH_MAX;
// 將logo圖片按martix設置的信息縮放
Matrix m = new Matrix();
/*
* 給的源碼是,由於CSDN上傳的資源不能改動,這里注意改一下
* float sx = (float) 2*logoHaleWidth / logoWidth;
* float sy = (float) 2*logoHaleHeight / logoHeight;
*/
float sx = (float) logoHaleWidth / logoWidth;
float sy = (float) logoHaleHeight / logoHeight;
m.setScale(sx, sy);// 設置縮放信息
Bitmap newLogoBitmap = Bitmap.createBitmap(logoBitmap, 0, 0, logoWidth,
logoHeight, m, false);
int newLogoWidth = newLogoBitmap.getWidth();
int newLogoHeight = newLogoBitmap.getHeight();
Hashtable hints = new Hashtable();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);//設置容錯級別,H為最高
hints.put(EncodeHintType.MAX_SIZE, LOGO_WIDTH_MAX);// 設置圖片的最大值
hints.put(EncodeHintType.MIN_SIZE, LOGO_WIDTH_MIN);// 設置圖片的最小值
hints.put(EncodeHintType.MARGIN, 2);//設置白色邊距值
// 生成二維矩陣,編碼時指定大小,不要生成了圖片以后再進行縮放,這樣會模糊導致識別失敗
BitMatrix matrix = new MultiFormatWriter().encode(content,
BarcodeFormat.QR_CODE, CODE_WIDTH, CODE_WIDTH, hints);
int width = matrix.getWidth();
int height = matrix.getHeight();
int halfW = width / 2;
int halfH = height / 2;
// 二維矩陣轉為一維像素數組,也就是一直橫着排了
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
/*
* 取值范圍,可以畫圖理解下
* halfW + newLogoWidth / 2 - (halfW - newLogoWidth / 2) = newLogoWidth
* halfH + newLogoHeight / 2 - (halfH - newLogoHeight) = newLogoHeight
*/
if (x > halfW - newLogoWidth / 2&& x < halfW + newLogoWidth / 2
&& y > halfH - newLogoHeight / 2 && y < halfH + newLogoHeight / 2) {// 該位置用於存放圖片信息
/*
* 記錄圖片每個像素信息
* halfW - newLogoWidth / 2 < x < halfW + newLogoWidth / 2
* --> 0 < x - halfW + newLogoWidth / 2 < newLogoWidth
* halfH - newLogoHeight / 2 < y < halfH + newLogoHeight / 2
* -->0 < y - halfH + newLogoHeight / 2 < newLogoHeight
* 剛好取值newLogoBitmap。getPixel(0-newLogoWidth,0-newLogoHeight);
*/
pixels[y * width + x] = newLogoBitmap.getPixel(
x - halfW + newLogoWidth / 2, y - halfH + newLogoHeight / 2);
} else {
pixels[y * width + x] = matrix.get(x, y) ? BLACK: WHITE;// 設置信息
}
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
// 通過像素數組生成bitmap,具體參考api
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
在實際操作當中我們的logo會遮擋中間部分的二維碼,可能導致二維碼失效,這個不用太擔心,二維碼識別有容錯級別的,我們將容錯級別設置為最大hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H),但是logo過大遮擋住關鍵信息還是會導致二維碼失效,因此我們需要控制logo在二維碼中占得比例,通過常量LOGO_WIDTH_MAX最大為二維碼寬度的20%、LOGO_WIDTH_MIN最小為二維碼寬度的10%,具體大小還是以縮放后的logo寬高為准。然后最關鍵的是怎么將帶白色邊框的logo放入二維碼中間,在代碼中可以看到通過雙重for循環來將二維矩陣轉換為一維像素數組,注釋比較詳細,就不多講了。
3、調用系統分享Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, imageFileUri);
shareIntent.setType("image/*");
//自定義提示語 startActivity(Intent.createChooser(shareIntent, "分享到"));
從調用系統分享圖片的代碼可以看出我們需要先存儲生成的二維碼圖片,通過saveBitmap(Bitmap bitmap, String bitName)保存圖片。/**
* 生成的二維碼圖片存儲的URI
*/
private Uri imageFileUri;
/**
* 保存生成的二維碼圖片
*/
private void saveBitmap(Bitmap bitmap, String bitName){
//獲取與應用相關聯的路徑
String imageFilePath = getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath();
File imageFile = new File(imageFilePath,"/" + bitName);// 通過路徑創建保存文件
imageFileUri = Uri.fromFile(imageFile);
if (imageFile.exists()) {
imageFile.delete();
}
FileOutputStream out;
try {
out = new FileOutputStream(imageFile);
if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)) {
out.flush();
out.close();
}
} catch (Exception e) {
}
}
好了,模擬器沒有QQ之類的,調用系統分享或其他具體操作請下載demo演示
We have to be greater than what we suffer.