博客轉載自:https://blog.csdn.net/skillcollege/article/details/38855023
什么是ZBar?
ZBar是一個開源庫,用於掃描、讀取二維碼和條形碼。支持的二維碼包括:EAN/UPC,QR等。
如果你是一個iPhone應用開發人員,做到二維碼模塊的時候,是不是會考慮ZBar開源項目來助你一臂之力呢?可是我這里說的是Android平台的開發,我為什么提到ZBar項目呢,難道我要用ZBar在Android平台掃描二維碼嗎?對的,沒有錯!這將會是一個極其不錯的選擇。為什么這么說呢,不是很多Android開發都是用Z*來解析二維碼的么?好吧,Z*是我下一篇文章要寫的,這里先拋磚引玉說一點點。我將Z*和ZBar做一個比較,說說它們的優缺點,便於大家的取舍。
- Z*項目的示例程序對於攝像頭的控制寫的非常全面,ZBar的沒有
- ZBar基於C語言編寫,解碼效率高於Z*項目
- ZBar是日本人寫的,對於中文解析會亂碼這個肯定有人遇到過的,Z*不會亂碼
- 掃描框的繪制,Z*的掃描框繪制是自定義View的,截取區域不好控制,ZBar的可以自定義,只要你會計算截取區
下載ZBar項目
編寫ZBar示例程序
1. 着重介紹一下掃描截取界面的計算

pt: 預覽圖中二維碼圖片的左上頂點坐標,也就是手機中相機預覽中看到的待掃描二維碼的位置 qrheight: 預覽圖中二維碼圖片的高度 qrwidth: 預覽圖中二維碼圖片的寬度 pheight:預覽圖的高度,也即camera的分辨率高度 pwidth: 預覽圖的寬度,也即camera的分辨率寬度 st: 布局文件中掃描框的左上頂點坐標 sheight: 布局文件中掃描框的高度 swidth: 布局文件中掃描框的寬度 cheight:布局文件中相機預覽控件的高度 cwidth: 布局文件中相機預覽控件的寬度
其中存在這樣一個等比例公式
ptx / pwidth = stx / cwidth pty / pheight = sty / cheight qrwidth / pwidth = swidth / cwidth qrheight / pheight = sheight / cheight
並外一種表達形式
ptx = stx * pwidth / cwidth ; pty = sty * pheight / cheight ; qrwidth = swidth * pwidth / cwidth ; qrheight = sheight * pheight / cheight ;
以上ptx,pty,qrwidth,qrheight四個參數也就是ZBar中解碼是需要crop時傳入的四個參數,如此便知道了截取區域應該如何計算了。這樣掃描的靈活性都大大增強了
2. ZBar中文亂碼的解決
ZBar掃描含有中文的二維碼圖片時,結果是亂碼的,所以需要修改c文件重新編譯打包so文件才行
a. 需要修改的文件是zbar/qrcode/qrdextxt.c文件
/*This is the encoding the standard says is the default.*/
latin1_cd=iconv_open("UTF-8","ISO8859-1");
修改為
/*This is the encoding the standard says is the default.*/
latin1_cd=iconv_open("UTF-8","GBK");
b. 重新編譯zbar生成so文件
這個真的需要一定的NDK開發經驗了,我個人只是了解一點點NDK的知識,所以在網上找到了一個大神的博客一步一步做下來才算是編譯完成了。

最后的編譯項目架構

最終生成的so文件
本地方法調用
package com.dtr.zbar.build;
public class ZBarDecoder {
static {
System.loadLibrary("ZBarDecoder");
}
/**
* 解碼方法
*
* @param data
* 圖片數據
* @param width
* 原始寬度
* @param height
* 原始高度
* @return
*/
public native String decodeRaw(byte[] data, int width, int height);
/**
* 解碼方法(需要裁剪圖片)
*
* @param data
* 圖片數據
* @param width
* 原始寬度
* @param height
* 原始高度
* @param x
* 截取的x坐標
* @param y
* 截取的y坐標
* @param cwidth
* 截取的區域寬度
* @param cheight
* 截取的區域高度
* @return
*/
public native String decodeCrop(byte[] data, int width, int height, int x, int y, int cwidth, int cheight);
}
我的ZBar編譯源程序代碼: 源碼下載
3. 編寫Android示例程序

其中CameraConfigurationManager和CameraManager兩個類是從ZXing項目中拷貝過來的,方便管理照相機,下一篇的ZXing項目中會更全面的使用ZXing中關於camera的管理類,本篇只是拋磚迎玉。
b.布局界面代碼
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/capture_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="@+id/capture_preview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/capture_mask_top"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_alignParentTop="true"
android:background="@drawable/shadow" />
<RelativeLayout
android:id="@+id/capture_crop_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerHorizontal="true"
android:layout_below="@id/capture_mask_top"
android:background="@drawable/qr_code_bg" >
<ImageView
android:id="@+id/capture_scan_line"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:src="@drawable/scan_line" />
</RelativeLayout>
<ImageView
android:id="@+id/capture_mask_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="@id/capture_crop_view"
android:background="@drawable/shadow" />
<ImageView
android:id="@+id/capture_mask_left"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_above="@id/capture_mask_bottom"
android:layout_alignParentLeft="true"
android:layout_below="@id/capture_mask_top"
android:layout_toLeftOf="@id/capture_crop_view"
android:background="@drawable/shadow" />
<ImageView
android:id="@+id/capture_mask_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_above="@id/capture_mask_bottom"
android:layout_alignParentRight="true"
android:layout_below="@id/capture_mask_top"
android:layout_toRightOf="@id/capture_crop_view"
android:background="@drawable/shadow" />
</RelativeLayout>
<Button
android:id="@+id/capture_restart_scan"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:background="#66ffcc00"
android:gravity="center"
android:text="restart scan"
android:textColor="@android:color/white"
android:textSize="14sp" />
<TextView
android:id="@+id/capture_scan_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/capture_restart_scan"
android:layout_marginBottom="10dp"
android:gravity="center"
android:text="Scanning..."
android:textColor="@android:color/white"
android:textSize="14sp" />
</RelativeLayout>
c.掃描Activity關鍵代碼
package com.dtr.zbar.scan;
import java.io.IOException;
import java.lang.reflect.Field;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.dtr.zbar.build.ZBarDecoder;
public class CaptureActivity extends Activity {
private Camera mCamera;
private CameraPreview mPreview;
private Handler autoFocusHandler;
private CameraManager mCameraManager;
private TextView scanResult;
private FrameLayout scanPreview;
private Button scanRestart;
private RelativeLayout scanContainer;
private RelativeLayout scanCropView;
private ImageView scanLine;
private Rect mCropRect = null;
private boolean barcodeScanned = false;
private boolean previewing = true;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_capture);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
findViewById();
addEvents();
initViews();
}
private void findViewById() {
scanPreview = (FrameLayout) findViewById(R.id.capture_preview);
scanResult = (TextView) findViewById(R.id.capture_scan_result);
scanRestart = (Button) findViewById(R.id.capture_restart_scan);
scanContainer = (RelativeLayout) findViewById(R.id.capture_container);
scanCropView = (RelativeLayout) findViewById(R.id.capture_crop_view);
scanLine = (ImageView) findViewById(R.id.capture_scan_line);
}
private void addEvents() {
scanRestart.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (barcodeScanned) {
barcodeScanned = false;
scanResult.setText("Scanning...");
mCamera.setPreviewCallback(previewCb);
mCamera.startPreview();
previewing = true;
mCamera.autoFocus(autoFocusCB);
}
}
});
}
private void initViews() {
autoFocusHandler = new Handler();
mCameraManager = new CameraManager(this);
try {
mCameraManager.openDriver();
} catch (IOException e) {
e.printStackTrace();
}
mCamera = mCameraManager.getCamera();
mPreview = new CameraPreview(this, mCamera, previewCb, autoFocusCB);
scanPreview.addView(mPreview);
TranslateAnimation animation = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT,
0.85f);
animation.setDuration(3000);
animation.setRepeatCount(-1);
animation.setRepeatMode(Animation.REVERSE);
scanLine.startAnimation(animation);
}
public void onPause() {
super.onPause();
releaseCamera();
}
private void releaseCamera() {
if (mCamera != null) {
previewing = false;
mCamera.setPreviewCallback(null);
mCamera.release();
mCamera = null;
}
}
private Runnable doAutoFocus = new Runnable() {
public void run() {
if (previewing)
mCamera.autoFocus(autoFocusCB);
}
};
PreviewCallback previewCb = new PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
Size size = camera.getParameters().getPreviewSize();
// 這里需要將獲取的data翻轉一下,因為相機默認拿的的橫屏的數據
byte[] rotatedData = new byte[data.length];
for (int y = 0; y < size.height; y++) {
for (int x = 0; x < size.width; x++)
rotatedData[x * size.height + size.height - y - 1] = data[x + y * size.width];
}
// 寬高也要調整
int tmp = size.width;
size.width = size.height;
size.height = tmp;
initCrop();
ZBarDecoder zBarDecoder = new ZBarDecoder();
String result = zBarDecoder.decodeCrop(rotatedData, size.width, size.height, mCropRect.left, mCropRect.top, mCropRect.width(), mCropRect.height());
if (!TextUtils.isEmpty(result)) {
previewing = false;
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
scanResult.setText("barcode result " + result);
barcodeScanned = true;
}
}
};
// Mimic continuous auto-focusing
AutoFocusCallback autoFocusCB = new AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
autoFocusHandler.postDelayed(doAutoFocus, 1000);
}
};
/**
* 初始化截取的矩形區域
*/
private void initCrop() {
int cameraWidth = mCameraManager.getCameraResolution().y;
int cameraHeight = mCameraManager.getCameraResolution().x;
/** 獲取布局中掃描框的位置信息 */
int[] location = new int[2];
scanCropView.getLocationInWindow(location);
int cropLeft = location[0];
int cropTop = location[1] - getStatusBarHeight();
int cropWidth = scanCropView.getWidth();
int cropHeight = scanCropView.getHeight();
/** 獲取布局容器的寬高 */
int containerWidth = scanContainer.getWidth();
int containerHeight = scanContainer.getHeight();
/** 計算最終截取的矩形的左上角頂點x坐標 */
int x = cropLeft * cameraWidth / containerWidth;
/** 計算最終截取的矩形的左上角頂點y坐標 */
int y = cropTop * cameraHeight / containerHeight;
/** 計算最終截取的矩形的寬度 */
int width = cropWidth * cameraWidth / containerWidth;
/** 計算最終截取的矩形的高度 */
int height = cropHeight * cameraHeight / containerHeight;
/** 生成最終的截取的矩形 */
mCropRect = new Rect(x, y, width + x, height + y);
}
private int getStatusBarHeight() {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object obj = c.newInstance();
Field field = c.getField("status_bar_height");
int x = Integer.parseInt(field.get(obj).toString());
return getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
}
d.運行效果圖

