鸿蒙二维码开发Zxing

一,概述

鸿蒙中目前选用开源三方库Zxing进行二次封装开发来完成二维码扫描和生成,Zxing目前已经相当的成熟和稳定,是纯Java库,所以可以直接在鸿蒙工程中引用.

首先简单的概括一下二维码扫描需要的准备工作:

(1) 引入Zxing三方库或Zxing.jar包,推荐gradle引入方式。
api ‘com.google.zxing:core:3.4.0’

(2) 自定义二维码扫描视觉框,绘制黑色边界,聚焦框可以方便用户对准二维码,动态扫描移动线可提升用户体验。

(3) Camera封装,摄像头捕捉到二维码图片。

(4) 根据Zxing提供的api,对摄像头捕捉到的图片进行编码,解码。

下面详解准备工作和拍照后的流程:

二,自定义扫描框

简述视觉框几个开发流程,绘制黑色边界,绘制边框四角,绘制移动线,然后不断重绘让移动线移动。
绘制黑色边界,绘制边框四角都使用canvas.drawRect进行绘制。
绘制移动线canvas.drawPixelMapHolderRect进行绘制移动线设置与顶部的距离,每重绘一次,与顶部距离缩短一点,然后使用Eventhandler设置定时不断重绘后移动线视觉上便是动画。

三,捕捉二维码图片

3.1 SurfaceProvider预览支持

该开发需要在ability或slice中进行,因为需要SurfaceProvider来支持,该类相当于之前使用的SurfaceView为打开摄像头提供了一个预览视窗。

简述基本开发流程,首先实例一个SurfaceProvider,再做一些基本设置

private SurfaceProvider surfaceProvider;surfaceProvider = new SurfaceProvider(this);surfaceProvider.pinToZTop(false);
surfaceProvider.getSurfaceOps().get().addCallback(new SurfaceCallBack());
class SurfaceCallBack implements SurfaceOps.Callback {@Overridepublic void surfaceCreated(SurfaceOps callbackSurfaceOps) {if (callbackSurfaceOps != null) {callbackSurfaceOps.setFixedSize(SCREEN_WIDTH,SCREEN_HEIGHT );}openCamera();}@Overridepublic void surfaceChanged(SurfaceOps callbackSurfaceOps, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceOps callbackSurfaceOps) {}}

这里要重点谈一下SurfaceCallBack,因为这里会存在扫描二维码时图像拉伸的问题。

在拍照预览页面,预览照片的拉伸问题主要与下面两个因素有关:

SurfaceProvider的大小和 Camera中的Preview的大小

手机camera的尺寸大小为25601920(横屏,比例为:1.333)预览尺寸大小为640480(横屏,比例为1.333)
手机SurfaceProvider大小为1280720(横屏,比例为:1.777)预览尺寸大小为960720(横屏,比例为1.777)
SurfaceProvider的宽高比例跟camera preview的宽高比例不一样才会导致打开camera后,照相机会出现拉伸的情况。
解决的方法就是计算SurfaceProvider尺寸比例跟camera预览尺寸比例相同。

方案一:简单设置

在SurfaceCallBack中设callbackSurfaceOps.setFixedSize(height,width);

这里宽高与手机预览尺寸的宽高是反向比例设置的,这样就能大幅改善拉伸问题。

方案二:精确计算

可以大幅度减少误差,更精确的处理比例不匹配的问题。

callbackSurfaceOps.setFixedSize (size.width,size.height)
private Size getOptimalSize(CameraKit cameraKit, String camId, int screenWidth, int screenHeight) {List<Size> sizes = cameraKit.getCameraAbility(camId).getSupportedSizes(ImageFormat.YUV420_888);final double ASPECT_TOLERANCE = 0.1;//竖屏screenHeight/screenWidth,横屏是screenWidth/screenHeightdouble targetRatio = (double) screenHeight / screenWidth;Size optimalSize = null;double minDiff = Double.MAX_VALUE;int targetHeight = screenWidth;for (Size size : sizes) {double ratio = (double) size.width / size.height;if (Math.abs(ratio - targetHeight) < minDiff) {optimalSize = size;minDiff = Math.abs(size.height - targetHeight);}}if (optimalSize == null) {minDiff = Double.MAX_VALUE;for (Size size : sizes) {if (Math.abs(size.height - targetHeight) < minDiff) {optimalSize = size;minDiff = Math.abs(size.height - targetHeight);}}}return optimalSize;
}

在有横盘扫描需要的时候,又需要重新调换回来,有横屏扫描需求这里要做一下横竖屏判断。

3.2 ImageReceiver配置

其次,需要在SurfaceCallBack的surfaceCreated方法中初始化ImageReceiver ,CameraKit。

ImageReceiver主要用于最后回调方法中接收摄像头捕捉到的帧图。

imageReceiver = ImageReceiver.create(SCREEN_WIDTH, SCREEN_HEIGHT, ImageFormat.JPEG, IMAGE_RCV_CAPACITY);
imageReceiver.setImageArrivalListener(this);

这里也需要设置屏幕的宽高值,有横屏二维码扫描需求的也是需要判断横竖屏进行设置宽高。否则,横屏扫描二维码时会扫描不出来。
imageReceiver监听回调,就可以拿到摄像头捕捉到的图片。

@Override
public void onImageArrival(ImageReceiver imageReceiver) {try {Image image = imageReceiver.readLatestImage();if (image == null) {return;}if (isAnalyze) {image.release();return;}isAnalyze = true;Image.Component component = image.getComponent(ImageFormat.ComponentType.JPEG);byte[] bytes = new byte[component.remaining()];ByteBuffer buffer = component.getBuffer();buffer.get(bytes);ImageSource imageSource = ImageSource.create(bytes, new ImageSource.SourceOptions());ImageSource.DecodingOptions options = new ImageSource.DecodingOptions();options.rotateDegrees = 90f;options.desiredRegion =new Rect(viewfinderView.getFramingRect().top,viewfinderView.getFramingRect().left,viewfinderView.getFramingRect().getHeight(),viewfinderView.getFramingRect().getWidth());PixelMap map = imageSource.createPixelmap(options);saveImage(bytes);String result = CodeUtils.parseInfoFromBitmap(map);Log.debug("result=" + result);if (result != null) {showMap(map, result);return;}image.release();isAnalyze = false;} catch (Exception e) {Log.debug(e.getMessage());}
}

再通过我封装的方法CodeUtils.parseInfoFromBitmap(map);对图片进行解码,即可拿到二维码图片信息。

3.3 CameraKit调起摄像头

CameraKit主要开启摄像头,同时还可以根据不同需要在FrameConfig.Builder中配置。我们需要用循环帧方式捕获图片,因为单帧方式类似拍照会卡顿。

CameraKit cameraKit = CameraKit.getInstance(getApplicationContext());
String[] cameraList = cameraKit.getCameraIds();
String cameraId = null;
for (String id : cameraList) {if (cameraKit.getCameraInfo(id).getFacingType() == CameraInfo.FacingType.CAMERA_FACING_BACK) {cameraId = id;}
}
if (cameraId == null) {return;
}
CameraStateCallbackImpl cameraStateCallback = new CameraStateCallbackImpl();
cameraKit.createCamera(cameraId, cameraStateCallback, creamEventHandler);

其中一些摄像头的配置需要在FrameConfig.Builder中进行配置,启动循环帧捕获。

class CameraStateCallbackImpl extends CameraStateCallback {CameraStateCallbackImpl() {}@Overridepublic void onCreated(Camera camera) {Log.debug("create camera onCreated");Log.debug("surfaceProvider==null  =" + (surfaceProvider));if (surfaceProvider == null) {return;}previewSurface = surfaceProvider.getSurfaceOps().get().getSurface();if (previewSurface == null) {Log.debug("create camera filed, preview surface is null");return;}// Wait until the preview surface is created.try {Thread.sleep(200);} catch (InterruptedException e) {Log.debug("Waiting to be interrupted");}CameraConfig.Builder cameraConfigBuilder = camera.getCameraConfigBuilder();cameraConfigBuilder.addSurface(previewSurface);cameraConfigBuilder.addSurface(imageReceiver.getRecevingSurface());camera.configure(cameraConfigBuilder.build());cameraDevice = camera;framePreviewConfigBuilder = camera.getFrameConfigBuilder(FRAME_CONFIG_PREVIEW);framePreviewConfigBuilder.addSurface(imageReceiver.getRecevingSurface());framePreviewConfigBuilder.addSurface(previewSurface);camera.triggerLoopingCapture(framePreviewConfigBuilder.build());}@Overridepublic void onConfigured(Camera camera) {Log.debug("onConfigured....");framePreviewConfigBuilder.setFlashMode(FLASH_OPEN);framePreviewConfigBuilder.addSurface(previewSurface);camera.triggerLoopingCapture(framePreviewConfigBuilder.build());}
}@Override
protected void onStop() {super.onStop();if (cameraDevice != null) {framePreviewConfigBuilder = null;try {cameraDevice.release();cameraDevice = null;surfaceProvider.clearFocus();surfaceProvider.removeFromWindow();surfaceProvider = null;} catch (Exception e) {Log.debug( e.getMessage());}}
}

初始化工作的配置好之后,先将SurfaceProvider添加到界面组件中,然后再把自定义视图框,添加到组件顶层即可。打开界面开始扫描二维码,从imageReceiver接口中拿到的图片信息,使用Zxing提供的方法进行解码后即可拿到需要的字符串信息。

下面简述一下如何解码。

四,Zxing解码和编码

4.1 二维码解码

解码是将扫描二维码图片解码为字符串信息。

4.1.1 像素数组采集

对摄像头捕捉到的图片帧转成PixelMap,然后用PixelMap使用Zxing提供的接口LuminanceSource进行像素数组采集。

public class BitmapLuminanceSource extends LuminanceSource {private byte[] bitmapPixels;public BitmapLuminanceSource(PixelMap pixelMap) {super(pixelMap.getImageInfo().size.width, pixelMap.getImageInfo().size.height);// 首先,要取得该图片的像素数组内容int[] data = new int[getWidth() * getHeight()];this.bitmapPixels = new byte[getWidth() * getHeight()];pixelMap.readPixels(data, 0, getWidth(), new Rect(0, 0, getWidth(), getHeight()));// 将int数组转换为byte数组,也就是取像素值中蓝色值部分作为辨析内容for (int i = 0; i < data.length; i++) {this.bitmapPixels[i] = (byte) data[i];}}@Overridepublic byte[] getMatrix() {// 返回我们生成好的像素数据return bitmapPixels;}@Overridepublic byte[] getRow(int y, byte[] row) {// 这里要得到指定行的像素数据System.arraycopy(bitmapPixels, y * getWidth(), row, 0, getWidth());return row;}
}

4.1.2 算法解析出数据源

然后将像素数据交给zxing提供的分析算法,HybridBinarizer和GlobalHistogramBinarizer算法。

new HybridBinarizer(new BitmapLuminanceSource(pixelMap))
或
new GlobalHistogramBinarizer(new BitmapLuminanceSource(pixelMap))

zxing官方默认的HybridBinarizer,两者区别HybridBinarizer算法执行效率上要慢一些,但是更有效,专门针对黑白相间的图像设计,也更适用于有阴影和渐变的二维码图像。GlobalHistogramBinarizer算法适用于低端机,对手机要求不高,识别速度快,精度高,但是无法处理阴影和渐变两个情况。

默认使用HybridBinarizer算法解析数据源。

4.1.3 数据转换为图像比特流

通过算法得到的数据,使用BinaryBitmap构造二值图像比特流。

BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BitmapLuminanceSource(pixelMap)));

4.1.4 图像解析参数配置

然后使用MultiFormatReader来解析图像,MultiFormatReader可以解析很多种数据格式,因此还要对它进行参数设置。

MultiFormatReader multiFormatReader = new MultiFormatReader();// 解码的参数
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
// 可以解析的编码类型
Vector<BarcodeFormat> decodeFormats = new Vector<BarcodeFormat>();
if (decodeFormats.isEmpty()) {decodeFormats = new Vector<BarcodeFormat>();// 这里设置可扫描的类型,我这里选择了都支持decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
// 设置继续的字符编码格式为UTF8
hints.put(DecodeHintType.CHARACTER_SET, "UTF8");
// 设置解析配置参数
multiFormatReader.setHints(hints);

4.1.5 获取最后的解析信息

最后参数设置好之后使用MultiFormatReader进行解析获取Result。

try {BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BitmapLuminanceSource(map)));Result rawResult = multiFormatReader.decodeWithState(binaryBitmap);if (rawResult != null) {result = rawResult.getText();}
} catch (Exception ignored) {Log.error(ignored.getMessage());
}

最后的result就是最后二维码扫描解析后得到字符串信息。

这里贴上整个解析过程代码:

public static String parseInfoFromBitmap(PixelMap map) {String result = null;MultiFormatReader multiFormatReader = new MultiFormatReader();// 解码的参数Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);// 可以解析的编码类型Vector<BarcodeFormat> decodeFormats = new Vector<BarcodeFormat>();if (decodeFormats.isEmpty()) {decodeFormats = new Vector<BarcodeFormat>();// 这里设置可扫描的类型,我这里选择了都支持decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS);decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS);decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS);}hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);// 设置继续的字符编码格式为UTF8hints.put(DecodeHintType.CHARACTER_SET, "UTF8");// 设置解析配置参数multiFormatReader.setHints(hints);try {BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(new BitmapLuminanceSource(map)));Result rawResult = multiFormatReader.decodeWithState(binaryBitmap);if (rawResult != null) {result = rawResult.getText();}} catch (Exception ignored) {Log.error(ignored.getMessage());}return result;
}DecodeFormatManager解码类型的代码:
public class DecodeFormatManager {private static final Pattern COMMA_PATTERN = Pattern.compile(",");private static final Vector<BarcodeFormat> PRODUCT_FORMATS;/*** one 格式*/public static final Vector<BarcodeFormat> ONE_D_FORMATS;/*** qr 格式*/public static final Vector<BarcodeFormat> QR_CODE_FORMATS;/*** data 格式*/public static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;static {PRODUCT_FORMATS = new Vector<BarcodeFormat>(5);PRODUCT_FORMATS.add(BarcodeFormat.UPC_A);PRODUCT_FORMATS.add(BarcodeFormat.UPC_E);PRODUCT_FORMATS.add(BarcodeFormat.EAN_13);PRODUCT_FORMATS.add(BarcodeFormat.EAN_8);PRODUCT_FORMATS.add(BarcodeFormat.RSS_14);ONE_D_FORMATS = new Vector<BarcodeFormat>(PRODUCT_FORMATS.size() + 4);ONE_D_FORMATS.addAll(PRODUCT_FORMATS);ONE_D_FORMATS.add(BarcodeFormat.CODE_39);ONE_D_FORMATS.add(BarcodeFormat.CODE_93);ONE_D_FORMATS.add(BarcodeFormat.CODE_128);ONE_D_FORMATS.add(BarcodeFormat.ITF);QR_CODE_FORMATS = new Vector<BarcodeFormat>(1);QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);DATA_MATRIX_FORMATS = new Vector<BarcodeFormat>(1);DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);}private DecodeFormatManager() {}
}

4.2编码二维码

编码:编码是将字符串生成二维码,相当于解码一个逆向的过程。

4.2.1 建立比特矩阵

核心点就是使用Zxing提供的MultiFormatWriter来创建比特矩阵。

BitMatrix matrix = new MultiFormatWriter().encode(str, BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight);

4.2.2 构造颜色数组

然后矩阵数据构造一个颜色数组。

int width = matrix.getWidth();
int height = matrix.getHeight();
int[] pixels = new int[width * height];for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {if (matrix.get(x, y)) {pixels[y * width + x] = BLACK;}}
}

4.2.3 通过颜色数组创建图片

PixelMap拿到颜色数组创建一个PixelMap图片。
贴上编码整个源码如下:

public static PixelMap createQRCode(String str, int widthAndHeight) throws WriterException {Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>();hints.put(EncodeHintType.CHARACTER_SET, "utf-8");BitMatrix matrix = new MultiFormatWriter().encode(str, BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight);int width = matrix.getWidth();int height = matrix.getHeight();int[] pixels = new int[width * height];for (int y = 0; y < height; y++) {for (int x = 0; x < width; x++) {if (matrix.get(x, y)) {pixels[y * width + x] = BLACK;}}}PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();options.size = new Size(width, height);return PixelMap.create(pixels, options);
}

最后拿到PixelMap去显示就是一张二维码图片了。效果图如下:

五,总结

整个流程简述就是通过手机摄像头一帧一帧的捕捉到对准的二维码图片,然后将图片使用封装好的解码方法进行解码,如果能够解码出字符串,即完成扫描,输出字符串信息。因为整个流程稍微有些复杂需要解释清楚,所以贴上的代码量还是挺大的,需要很耐心的看。整个封装集成流程,需要几个要注意的地方。

(1) 二维码扫描横竖屏的问题,需要判断横竖屏需要对imageReceiver初始化,SurfaceCallBack初始化的时候动态设置宽高比的问题。

(2) 在二维码扫描的时候图像会出现拉伸的情况,在初始化相机尺寸的时候分别对预览尺寸值和图片尺寸值都设定为比例最接近屏幕尺寸的尺寸值,需要在SurfaceCallBack中设置宽高比。这里仅仅是将屏幕宽和高的数值进行倒置强行设置,便大幅改善,如果需要更精准需要动态计算。
如需更多查看源码,请移步gitee开源项目:ohos-zblibrary项目下的二维码封装库qrcodelibrary。
https://gitee.com/openharmony-tpc/ohos-ZBLibrary
扫描成功拿到解析结果效果图如下:

鸿蒙二维码开发Zxing相关推荐

  1. 生成和扫描二维码(ZXing库)

    生成和扫描二维码(ZXing库) 一.ZXing概述 ZXing是谷歌自己推出的一个开源源码的二维码框架,可以实现使用手机的摄像头完成条形码的扫描和解码. 二.整合ZXing框架 将预先获取的core ...

  2. 技术解读 一维码,二维码,zxing

    一维码,二维码,zxing 什么是一维码.二维码?一维码就是商品包装盒上的条形码,例如:书本后面的条形码,在真维斯或者其他等品牌店的衣服标签上都可以看到,一维码的应用已经很广泛了:而二维码就是.... ...

  3. 二维码之zxing仿新浪微博二维码

    在前言中最后部分,提到了二维码开发工具资源ZXing.网上有它最新1.7版的源码,感兴趣的可以下载下来看看,要打包生成core比较麻烦,网上有相关教程.嫌麻烦的朋友,可以去我的资源里下载Java版的c ...

  4. Java简单的生成/解析二维码(zxing qrcode)

    Hi I'm Shendi Java简单的生成/解析二维码(zxing qrcode) 在之前使用 qrcode.js 方式生成二维码,但在不同设备上难免会有一些兼容问题,于是改为后端(Java)生成 ...

  5. Unity二维码插件 ZXing

    1.二维码常见的生成与识别途径 1.草料二维码 https://cli.im/text 2.在软件中实现生成和扫描二维码 使用zxing实现 zxing是一个用java写的开源项目,zxing.net ...

  6. 二维码之zxing二维码解析图片资源

    前面讲了如何利用zxing生成二维码图像以及仿照新浪微博方式生成二维码.接下来,就要开始谈到如何利用zxing解析二维码图像. zxing针对不同开发平台,都给出了解析二维码的例子,我这里只聊聊关于a ...

  7. 短短60行代码搞定鸿蒙“二维码扫描”功能!

    开发者(KaiFaX) 面向全栈工程师的开发者专注于前端.Java/Python/Go/PHP的技术社区 可以实现的效果就是打开摄像头扫描一张二维码图片然后显示二维码里面的内容,看个视频一睹为快吧(界 ...

  8. android二维码开发的实用案例

     最近公司需要实现二维码扫描的功能,可能这个大家一看就觉得.论坛Demo一大把,随便copy一个就可以使用了!起初我也是这么认为,随便找了一个改改!!后来发现Bug太多,有太多不理想的东西..索性 ...

  9. 二维码扫描ZXing简化

    最近项目中有需要用到二维码扫描功能,于是查了相关资料,也没有过多地研究ZXing源码,只是有了最简单的功能,因为下载大牛的demo已经完全实现了功能,只是对其中的扫描线做了更改,需要的朋友可以直接使用 ...

最新文章

  1. iOS使用多线程提高数据并发访问 之七
  2. 【iOS与EV3混合机器人编程一系列五个】iOS_WiFi_EV3_Library 解剖连接EV3
  3. 基于模板引擎的代码生成器Smart Code预览
  4. windows下配置mysql数据库_mysql数据库1-windows下mysql安装及配置
  5. S5PV210开发 -- 通过 DNW、fastboot 烧写
  6. java 什么时候依赖注入_玩框架java依赖注入 – 何时使用单例
  7. Event Logging 技术简介(转载)
  8. android 自定义指南针,如何在android中制作自定义指南针视图
  9. 买了社保,再买农村医保是不是多余?
  10. 【AIQ合集】人工智能技术学习资料年度整理大合集电子书 PDF下载
  11. Spring IOC源码分析
  12. 地铁 综合监控设备 及其所属子系统
  13. 请使用hive udf或者spark udf实现父子关系树图分析,列举出所有的路径
  14. A problem occurred configuring root project ‘xxx‘.
  15. 我的世界服务器登录显示motd,[信息]ColorMOTD —— 究极Motd插件,外带反压测[1.7-1.8]...
  16. matlab y 0,用MATLAB算y-2y-3y=0的解
  17. python怎样删除某一行_python删除某一行
  18. 在互联网行业断断续续这四年间
  19. 怎么把图片文件压缩到最小?
  20. Linux_进程相关概念

热门文章

  1. RecyclerView 源码分析(一):Recycler
  2. 六一,用前端做个小游戏回味童年
  3. 社交电商平台就是“分享经济+新零售+粉丝经济
  4. Web html5 app ,桌面,Webtop Html5 桌面App开发 -- 整合人人网登陆
  5. Python16-面向对象类和对象构造函数
  6. 北森秋招(代做与题库都可)
  7. 10.Go语言基础之结构体
  8. Eclipse 安装了 Groovy 开发插件之后,双击打不开 .java 文件了
  9. win10禁用Windows Defender
  10. 微型投影仪哪个牌子好? 国产家用投影仪什么品牌的好