鴻蒙開源第三方組件—Zbar_ohos條形碼閱讀器
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
前言
基于安卓平臺的條形碼閱讀器控件ZBar(https://github.com/ZBar/ZBar),實(shí)現(xiàn)了鴻蒙化遷移和重構(gòu),代碼已經(jīng)開源到(https://gitee.com/isrc_ohos/ZBar),歡迎各位下載使用并提出寶貴意見!
背景
Zbar-ohos是基于鴻蒙系統(tǒng)的條形碼閱讀器,支持EAN-13 / UPC-A、UPC-E、EAN-8、Code 128、CODE39、Codabar和QR碼的識別,目前已經(jīng)廣泛應(yīng)用于掃碼登記、掃碼觀影、掃碼登錄等多個(gè)領(lǐng)域。
組件效果展示
1、添加權(quán)限
打開軟件后,會顯示如圖1所示的添加攝像頭權(quán)限提示。點(diǎn)擊“始終允許”按鈕,并重啟該軟件(刷新UI界面),即可掃描條形碼。
圖1 掃描二維碼界面
2、掃描效果
掃描界面包含兩個(gè)部分:對準(zhǔn)器和狀態(tài)欄。對準(zhǔn)器顯示攝像頭拍攝的畫面,條形碼需要置于此范圍內(nèi),才可以被掃描。狀態(tài)欄用于顯示當(dāng)前的掃描狀態(tài)或掃描結(jié)果。
(1)一維條形碼掃描
一維條形碼一般是在水平方向上表達(dá)信息,而在垂直方向不表達(dá)任何信息。為了方便對準(zhǔn)器的讀取,其高度通常是固定的。
ZBar組件掃描一維條形碼的效果圖2所示。攝像頭掃到條形碼時(shí),下方狀態(tài)欄的顯示內(nèi)容由“掃描中”更新為條形碼的掃描結(jié)果。掃到下一個(gè)條碼時(shí),狀態(tài)欄的掃描結(jié)果也實(shí)時(shí)更新。
圖2 條形碼掃描結(jié)果
(2)二維條形碼掃描
二維條形碼在水平和垂直方向上都表示信息,信息容量大,結(jié)構(gòu)通常為方形結(jié)構(gòu),保密級別高,可直接顯示英文、中文、數(shù)字、符號、圖型等。
ZBar組件掃描二維條形碼的效果圖3所示。掃描過程與上述一維條形碼一致,狀態(tài)欄會顯示二維條形碼的掃描結(jié)果。
圖3 二維碼掃描結(jié)果
Sample解析
Sample部分首先創(chuàng)建相機(jī)設(shè)備并合理配置,然后將相機(jī)獲得的原始數(shù)據(jù)傳遞給Library掃描處理,最后獲取掃描結(jié)果并顯示在屏幕上。下面對Sample部分的代碼進(jìn)行具體解釋:
1、生成Camera類對象
CameraKit類可以提供使用相機(jī)功能的條目,CameraStateCallbackImpl 類是相機(jī)創(chuàng)建和相機(jī)運(yùn)行時(shí)的回調(diào)。此處通過CameraKit類來生成Camera對象,不同尋常的是,CameraKit類并沒有將Camera對象直接返回,而是需要從CameraStateCallbackImpl 回調(diào)中獲取。
- private void openCamera(){
- // 獲取 CameraKit 對象
- cameraKit = CameraKit.getInstance(this);
- if (cameraKit == null) {
- return;
- }
- try {
- // 獲取當(dāng)前設(shè)備的邏輯相機(jī)列表cameraIds
- String[] cameraIds = cameraKit.getCameraIds();
- if (cameraIds.length <= 0) {
- System.out.println("cameraIds size is 0");
- }
- // 用于相機(jī)創(chuàng)建和相機(jī)運(yùn)行的回調(diào)
- CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
- if(cameraStateCallback ==null) {
- System.out.println("cameraStateCallback is null");
- }
- // 創(chuàng)建用于運(yùn)行相機(jī)的線程
- EventHandler eventHandler = new EventHandler(EventRunner.create("CameraCb"));
- if(eventHandler ==null) {
- System.out.println("eventHandler is null");
- }
- // 創(chuàng)建相機(jī)
- cameraKit.createCamera(cameraIds[0], cameraStateCallback, eventHandler);
- } catch (IllegalStateException e) {
- System.out.println("getCameraIds fail");
- }
- }
2、綁定相機(jī)的Surface
Surface用于實(shí)現(xiàn)相機(jī)的預(yù)覽、拍照、錄像等功能。此處為相機(jī)添加:previewSurface和 dataSurface。前者用來展示相機(jī)拍攝到的界面;后者用來讀取并處理相機(jī)拍攝到的數(shù)據(jù)信息。
- private final class CameraStateCallbackImpl extends CameraStateCallback {
- // 相機(jī)創(chuàng)建和相機(jī)運(yùn)行時(shí)的回調(diào)
- @Override
- public void onCreated(Camera camera) {
- mcamera = camera;//獲取到Camera 對象
- CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();
- if (cameraConfigBuilder == null) {
- System.out.println("onCreated cameraConfigBuilder is null");
- return;
- }
- // 配置預(yù)覽的 Surface
- cameraConfigBuilder.addSurface(previewSurface);
- // 配置處理數(shù)據(jù)的Surface
- dataSurface = imageReceiver.getRecevingSurface();
- cameraConfigBuilder.addSurface(dataSurface);
- try {
- // 相機(jī)設(shè)備配置
- camera.configure(cameraConfigBuilder.build());
- } catch (IllegalArgumentException e) {
- System.out.println("Argument Exception");
- } catch (IllegalStateException e) {
- System.out.println("State Exception");
- }
- }
- }
3、開啟循環(huán)幀捕獲
用戶一般在畫面生成后,才執(zhí)行拍照或者其他操作。開啟循環(huán)幀捕獲后,dataSurface可以獲得來自相機(jī)的數(shù)據(jù)。
- @Override
- public void onConfigured(Camera camera) {
- // 獲取預(yù)覽配置模板
- FrameConfig.Builder frameConfigBuilder = mcamera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);
- // 配置預(yù)覽 Surface
- frameConfigBuilder.addSurface(previewSurface);
- // 配置拍照的 Surface
- frameConfigBuilder.addSurface(dataSurface);
- try {
- // 啟動循環(huán)幀捕獲
- int triggerId = mcamera.triggerLoopingCapture(frameConfigBuilder.build());
- } catch (IllegalArgumentException e) {
- System.out.println("Argument Exception");
- } catch (IllegalStateException e) {
- System.out.println("State Exception");
- }
- }
4、掃描相機(jī)數(shù)據(jù)
dataSurface中的數(shù)據(jù)為相機(jī)原始數(shù)據(jù),其格式為YUV420,需要將其封裝為Image類的數(shù)據(jù)才能執(zhí)行傳入ImageScanner類進(jìn)行正式掃描。
- // 相機(jī)原始數(shù)據(jù)封裝為Image數(shù)據(jù)
- Image barcode = new Image(mImage.getImageSize().width,mImage.getImageSize().height, "Y800");
- barcode.setData(YUV_DATA);
- //Image數(shù)據(jù)掃描
- int result = scanner.scanImage(barcode);
5、顯示預(yù)覽數(shù)據(jù)的掃描結(jié)果
由于對準(zhǔn)器中的條形碼可能不止一個(gè),ImageScanner類的掃描結(jié)果可能也有多個(gè),因此最后返回的掃描結(jié)果是SymbolSet類型,此數(shù)據(jù)類型是可以盛納多個(gè)Symbol數(shù)據(jù)的容器,每個(gè)Symbol數(shù)據(jù)代表一個(gè)條形碼的掃描結(jié)果。
- //創(chuàng)建可以盛納多個(gè)Symbol數(shù)據(jù)的容器SymbolSet
- SymbolSet syms = scanner.getResults();
- //遍歷SymbolSet 中的每個(gè)元素
- for (Symbol sym : syms) {
- handler.postTask(new Runnable() {
- @Override
- public void run() {
- scanText.setText("掃描結(jié)果:" + sym.getData());//獲取Symbol中的信息
- scanText.invalidate();
- }
- });
Library解析
Library部分主要是對dataSurface的數(shù)據(jù)進(jìn)行掃描,此處主要涉及兩個(gè)功能:(1)相機(jī)原始數(shù)據(jù)封裝為Image數(shù)據(jù);(2)對Image數(shù)據(jù)進(jìn)行掃描。由于這部分主要由C語言實(shí)現(xiàn),所以此處只解析大概原理,展示主要接口,不再進(jìn)行底層代碼的展示。
(1)相機(jī)原始數(shù)據(jù)封裝為Image數(shù)據(jù)
Image支持多種數(shù)據(jù)格式,包括常見的YUV以及RGB數(shù)據(jù)。此處需要的Image數(shù)據(jù)是“Y800”類型或者“GRAY”類型,即條形碼的掃描數(shù)據(jù)僅需要圖像的灰度數(shù)據(jù)。
- public native void setData(byte[] data);
(2)對Image數(shù)據(jù)進(jìn)行掃描
使用scanImage()方法對傳入的Image數(shù)據(jù)進(jìn)行掃描。該過程首先對傳入的圖像進(jìn)行配置校驗(yàn),然后以一個(gè)像素點(diǎn)為增量逐行掃描,掃描路徑為Z字型,并且完成對掃描數(shù)據(jù)的濾波,求取邊緣梯度,梯度閾值自適應(yīng),確定邊緣等操作,最后將掃描數(shù)據(jù)轉(zhuǎn)化成明暗寬度流。 通過明暗寬度流的變化格律可以知道當(dāng)前正在被掃描的條形碼的種類,然后依據(jù)固定的解碼方法進(jìn)行解碼,便可得到條形碼信息。
- public native int scanImage(Image image);
項(xiàng)目貢獻(xiàn)人
陳叢笑 鄭森文 朱偉 陳美汝 張馨心
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)