2021-08-30 14:01:13
0
1. 介绍 本文档将介绍如何生成常用的二维码、如何识别图片中的二维码、如何实现二维码扫描功能。
场景介绍
- 医学或健康类应用:根据输入的体温数据,生成对应健康二维码。
- 社交或通讯类应用:根据公众号图片,识别图中二维码关注公众号。
- 购物或支付类应用:根据商家提供的付款二维码,扫描后进行支付功能。
2. 搭建HarmonyOS环境我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 开发者可以参考以下链接,完成设备调试的相关配置:
3. 代码结构解读 工程结构描述如下:
- qrlibrary
- core
GenerateCore:二维码生成核心类。
ScannerCore:二维码识别核心类。 - util
PixelMapUtil:图像处理工具类。
QRCodeUtil:辅助工具类。 - CameraPreview:相机预览。
- QRCodeView:二维码扫描核心类。
- ScanBoxView:扫描框布局类。
- slice
- CodeGenerationAbilitySlice:码生成示例。
- CodeIdentificationAbilitySlice:码识别示例。
- CodeScanningAbilitySlice:码扫描示例。
- MainAbilitySlice:首页页面入口。
- resources
- /base/layout
ability_code_generation.xml:码生成布局。
ability_code_identification.xml:码识别布局。
ability_code_scanning.xml:码扫描布局。
ability_main.xml:首页入口布局。 - /base/media:icon.png用于生成logo图标的图片,a~d.jpg为码识别需要使用的图片。line_broad.png和line_grid.png图片是自定义ScanBoxView类中使用的边框。
4. 二维码生成 如上图,常用的二维码有中英文二维码,不同颜色二维码和带有logo图标的二维码。
生成中英文二维码先通过IBarcodeDetector类生成二维码图片字节数组,再使用ImageSource类将字节数组解码为PixelMap图像。
步骤 1 - 调用VisionManager.init()方法,建立与能力引擎的连接。定义ConnectionCallback回调,能力引擎连接成功会触发onServiceConnect()方法执行。在此方法中实例化IBarcodeDetector接口。代码如下:
- private IBarcodeDetector barcodeDetector;
-
- private void initManager(Context context) {
- ConnectionCallback connectionCallback = new ConnectionCallback() {
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onServiceConnect() {
- barcodeDetector = VisionManager.getBarcodeDetector(context);
- }
- @Override
- public void onServiceDisconnect() {
- }
- };
- VisionManager.init(context, connectionCallback);
- }
复制代码
步骤 2 - 定义码生成字符串的内容content,码生成图像的尺寸size。调用IBarcodeDetector的detect()方法,生成二维码图片字节数组。代码如下:
- private byte[] generateQR(String content, int size) {
- byte[] byteArray = new byte[size * size * BYTE_SIZE];
- String val = content;
- try {
- // 解决中文不能识别的问题
- val = new String(content.getBytes("UTF-8"), "ISO8859-1");
- } catch (UnsupportedEncodingException e) {
- QRCodeUtil.error("error");
- }
- barcodeDetector.detect(val, byteArray, size, size);
- return byteArray;
- }
复制代码
步骤 3 - 调用ImageSource.create(byte[] data, ImageSource.SourceOptions opts)方法,创建图像源ImageSource对象。其中,data为生成额二维码字节数组,opts为解码参数。然后调用ImageSource的createPixelmap()方法,获取PixelMap图像对象。代码如下:
- public PixelMap generateCommonQRCode(String content, int size) {
- byte[] data = generateQR(content, size);
- ImageSource source = ImageSource.create(data, null);
- ImageSource.DecodingOptions opts = new ImageSource.DecodingOptions();
- opts.editable = true;
- PixelMap pixelMap = source.createPixelmap(opts);
- return pixelMap;
- }
复制代码
生成不同颜色二维码
默认生成PixelMap图像的颜色是黑色的,像素值为0xFF000000,只需要将其修改为自定义的颜色值。
步骤 1 - 调用PixelMap的getImageInfo()方法可以获取位图的大小,并循环遍历每个像素点。
- Size size = pixelMap.getImageInfo().size;
- for (int i = 0; i < size.width; i++) {
- for (int j = 0; j < size.height; j++) {
- // 更改像素点的值
- }
- }
复制代码
步骤 2 - 调用PixelMap的readPixel(Position pos)方法读取指定位置的像素值,Position描述为图像坐标。默认生成的像素值为0xFF000000。
- int color = pixelMap.readPixel(new Position(i, j));
- if (color == 0xFF000000) {
- // 在指定位置写入像素
- }
复制代码
步骤 3 - 定义需要更改颜色的变量,然后调用PixelMap的writePixel(Position pos, int color)方法向指定位置写入数据。
- public static final int QR_COLOR = 0xFF009900;
- pixelMap.writePixel(new Position(i, j), QR_COLOR);
复制代码
生成logo图标二维码
将logo图标对应的PixelMap图像,画在已经生成二维码PixelMap图像的中间。
步骤 1 - 调用getResourceManager().getResource()方法读取"resources/base/media/"目录下的资源图片,然后得到该资源图片的位图PixelMap对象。
- InputStream resource = getResourceManager().getResource(ResourceTable.Media_icon);
- ImageSource logoSource = ImageSource.create(resource, null);
- PixelMap logoMap = logoSource.createPixelmap(null);
复制代码
步骤 2 - 获取二维码PixelMap图像的大小,然后将logo的PixelMap图像缩小为0.14。代码如下:
- Size size = pixelMap.getImageInfo().size;
-
- PixelMap.InitializationOptions opts =new PixelMap.InitializationOptions();
- opts.size = new Size((int) (size.width * 0.14),(int) (size.height * 0.14));
- PixelMap logoPixelMap = PixelMap.create(logoMap, opts);
复制代码
步骤 3 - 使用Canvas在二维码PixelMap图像的中间绘制logo的PixelMap图像。代码如下:
- Canvas canvas = new Canvas(new Texture(pixelMap));
- int centerX = size.width / 2- logoSize.width / 2;
- int centerY = size.height / 2- logoSize.height / 2;
- canvas.drawPixelMapHolder(new PixelMapHolder(logoPixelMap), centerX, centerY, new Paint());
复制代码 5. 二维码识别
上面四张图中的二维码为上一节生成的,现在验证能否被识别出来。其中, 第二张图识别结果为"华为官方网址",其他三张识别结果均是http://www.huawei.com。
思路是将图片转换为PixelMap对象,将引入第三方库文件完成二维码识别功能。具体步骤如下:
步骤 1 - 在libs文件夹下引入下图两个.so文件(libiconv.so、libzbarjni.so)和一个.jar包文件(zbar.jar)。
步骤 2 - 将需要识别图片的PixelMap对象数据载入到net.sourceforge.zbar.Image对象中。代码如下:
- private Image processImage(PixelMap pixelMap) {
- int pWidth = pixelMap.getImageInfo().size.width;
- int pHeight = pixelMap.getImageInfo().size.height;
- // 初始化装载图像Image对象
- Image barcode = new Image(pWidth, pHeight, "RGB4");
- int[] pix = new int[pWidth * pHeight];
- // 将位图数据写入到Image中
- pixelMap.readPixels(pix, 0, pWidth, new ohos.media.image.common.Rect(0, 0, pWidth, pHeight));
- barcode.setData(pix);
-
- return barcode.convert("Y800");
- }
复制代码 步骤 3 - 初始化二维码识别器ImageScanner对象。代码如下:
- private ImageScanner mScanner;
-
- private void initImageScanner() {
- mScanner = new ImageScanner();
- mScanner.setConfig(0, Config.X_DENSITY, CONFIG_SIZE);
- mScanner.setConfig(0, Config.Y_DENSITY, CONFIG_SIZE);
- }
复制代码 步骤 4 - 通过调用ImageScanner对象的scanImage()方法,对图像对象Image进行处理,并调用其getResults()方法,得到二维码识别的结果。代码如下:
- private String processData(Image barcode) {
- String symData = null;
- if (mScanner.scanImage(barcode) == 0) {
- return symData;
- }
- for (Symbol symbol : mScanner.getResults()) {
- // 未能识别的格式继续遍历
- if (symbol.getType() == Symbol.NONE) {
- continue;
- }
- symData = new String(symbol.getDataBytes(), StandardCharsets.UTF_8);
- // 空数据继续遍历
- if (QRCodeUtil.isEmpty(symData)) {
- continue;
- }
- return symData;
- }
- return symData;
- }
复制代码
6. 二维码扫描 上图所示,开启摄像头来扫描二维码并识别结果。思路是采集相机的数据转为PixelMap对象,然后使用码识别的功能。
开启摄像头
步骤 1- 继承SurfaceProvider类,在构造函数调用getSurfaceOps().get().addCallback(this)这行代码,在其实现方法surfaceCreated()中,得到Surface对象,用来展示摄像头窗口。代码如下:
- public class CameraPreview extends SurfaceProvider implements SurfaceOps.Callback {
- private Surface previewSurface;
-
- public CameraPreview(Context context) {
- super(context);
- getSurfaceOps().get().addCallback(this);
- }
-
- @Override
- public void surfaceCreated(SurfaceOps surfaceOps) {
- previewSurface = surfaceOps.getSurface();
- }
- }
复制代码 步骤 2 - 通过CameraKit.getInstance()方法得到CameraKit对象,调用CameraKit的createCamera()方法,创建相机对象。代码如下:
- private void openCamera() {
- CameraKit cameraKit = CameraKit.getInstance(getContext());
- String[] cameraIds = cameraKit.getCameraIds();
- cameraKit.createCamera(cameraIds[0], new CameraStateCallbackImpl()
- new EventHandler(EventRunner.create("qr")));
- }
复制代码 步骤 3- 定义CameraStateCallback实现类,相机对象Camera创建成功会回调其onCreated()方法。然后调用CameraConfig.Builder的addSurface()方法,配置相机Surface预览画面。代码如下:
- private class CameraStateCallbackImpl extends CameraStateCallback {
- @Override
- public void onCreated(Camera camera) {
- super.onCreated(camera);
- CameraConfig.Builder cameraBuilder = camera.getCameraConfigBuilder();
-
- cameraBuilder.addSurface(previewSurface);
-
- camera.configure(cameraBuilder.build());
- }
- }
复制代码
数据采集与解析
使用ImageReceiver类来接收相机每帧数据,然后将每帧数据转化为PixelMap对象。具体思路如下:
步骤 1- 定义ImageReceiver.IImageArrivalListener类,在onImageArrival()方法中处理每一帧数据。代码如下:
- private ImageReceiver.IImageArrivalListener imageArrivalListener = new ImageReceiver.IImageArrivalListener() {
- @Override
- public void onImageArrival(ImageReceiver imageReceiver) {
- // 每帧数据图像接收
- }
- };
复制代码 步骤 2- 初始化ImageReceiver,并设置图像回调类IImageArrivalListener,第三个参数格式要配置为ImageFormat.JPEG格式。代码如下:
- ImageReceiver imageReceiver = ImageReceiver.create(SCREEN_HEIGHT, SCREEN_WIDTH, ImageFormat.JPEG, IMAGE_RCV_CAPACITY);
- imageReceiver.setImageArrivalListener(imageArrivalListener);
复制代码 步骤 3- 调用ImageReceiver的getRecevingSurface()方法得到Surface对象,将其配置在CameraConfig.Builder中,用来接收相机的每一帧数据。代码如下:
- @Override
- public void onCreated(Camera camera) {
- super.onCreated(camera);
- CameraConfig.Builder cameraBuilder = camera.getCameraConfigBuilder();
- Surface imageSurface = imageReceiver.getRecevingSurface();
- cameraBuilder.addSurface(imageSurface);
- }
复制代码 步骤 4- 调用ImageReceiver的readNextImage()得到ohos.media.image.Image图像,然后调用ohos.media.image.Image.Component类的read()方法,读取图像数据到字节数组中。代码如下:
- @Override
- public void onImageArrival(ImageReceiver imageReceiver) {
- ohos.media.image.Image image = imageReceiver.readNextImage();
- ohos.media.image.Image.Component component = image.getComponent(ImageFormat.ComponentType.JPEG);
- byte[] data = new byte[component.remaining()];
- component.read(data);
- }
复制代码 步骤 5- 最后使用ImageSource解码字节数组,得到PixelMap对象。代码如下:
- ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
- sourceOptions.formatHint = "image/jpg";
- ImageSource imageSource = ImageSource.create(data, sourceOptions);
- PixelMap pixelMap = imageSource.createPixelmap(null);
复制代码
7. 恭喜你
通过本教程的学习,你已学会了如何生成常用二维码、识别二维码、实现二维码扫描。
主要技术包括:
- EventHandler在子线程执行任务。
- 如何引入及使用.so文件。
- PixelMap位图解码使用。
- 相机数据采集与PixelMap转化。
8. 参考 Gitee源码
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。
侵权投诉