OpenHarmony应用实现二维码扫码识别

441 篇文章 3 订阅
330 篇文章 3 订阅

概念介绍

二维码的应用场景非常广泛,在购物应用中,消费者可以直接扫描商品二维码,浏览并购买产品,如图是购物应用的扫描二维码的页面。

本文就以 橘子购物示例应用 为例,来讲解OpenHarmony应用二维码开发相关的技术点。

我们先看下二维码相关的几个概念。

● 二维码生成

OpenHarmony应用框架提供了 QRCode组件 ,用于显示单个二维码的组件。该组件只能用于显示二维码,无法显示条码与解析码内容。

● 二维码解析

OpenHarmony提供了功能强大的三方库 @ohos/zxing,是一个解析/生成一维码/二维码的库。详细内容可以参考 @ohos/zxing 。

二维码解析时,通常有两种方式,使用相机拍摄获取图片或打开相册选取图片,然后图片解析合适的图片格式,进行二维码解析。

橘子购物示例应用 扫描二维码的示例图:

配置文件

了解了二维码相关的概念后,我们看下 橘子购物示例应用 的oh-package.json5配置文件。

在 橘子购物示例应用 中,实现首页二维码扫描的页面的文件位置为: entry/src/main/ets/pages/ScanPage.ets 。文件内容如下:

import { QRCodeScanComponent } from "@ohos/scan-component"
@Entry
@Component
struct Scan {
  build() {
    Column() {
      QRCodeScanComponent()
    }
  }
}

内容非常简单,主要是导入的自定义组件QRCodeScanComponent,这个组件的代码来自: 二维码扫描示例应用,后文我们这样分析如何开发这个二维码扫描应用。

从这一行,可以了解到OpenHarmony应用如何引用ohpm本地三方库。

"@ohos/scan-component":"file:../libs/ohos-qr-code-scan-1.0.1.har",

oh-package.json5配置文件片段如下:

{
  "license": "ISC",
  "devDependencies": {},
  "name": "product",
  "description": "example description",
  "repository": {},
  "version": "1.0.0",
  "dependencies": {
    "@ohos/http": "file:../libs/ohos-http-1.0.0.tgz",
    "@ohos/video-component": "file:../libs/ohos-video-component-1.0.5.tgz",
    "@ohos/details-page-component": "file:../feature/detailPage",
    "@ohos/notification": "file:../libs/ohos-notification-1.0.0.tgz",
    "@ohos/scan-component": "file:../libs/ohos-qr-code-scan-1.0.1.har",
    "@ohos/updatedialog": "file:../libs/ohos-updatedialog-1.0.0.tgz",
    "@ohos/enter-animation": "file:../libs/ohos-enter-animation-1.0.1.tgz",
    "@ohos/share-component": "file:../libs/ohos-sharecomponent-1.0.1.tgz",
    "@ohos/emitter": "file:../feature/emitter",
    "@ohos/navigation-component": "file:../feature/navigationHome"
  }
}

开发步骤

我们来看二维码扫描功能是如何开发的。

导入ohpm三方库

在开发前,我们需要导入ohpm组件库:@ohos/zxing。可以使用命令行方式导入ohpm install @ohos/zxing,也可以直接在文件 entry\oh-package.json5 中配置,如文件片段所示。

可以看出,二维码扫描的核心代码存放在Feature目录,是一个独立的module模块,方便复用:

“@ohos/feature-qr-code-scan”: “file:…/Feature” 。

文件 entry\oh-package.json5 片段:

"dependencies": {
  "@ohos/feature-qr-code-scan": "file:../Feature",
  "@ohos/zxing": "^2.0.0"
} 

相机服务

CameraService.ets 文件相机服务构造函数中,会创建一个图片接收器。

该图片接收器可以监听’imageArrival’事件,当相机拍照时会触发该事件。在监听事件的回调函数里,实现对拍照的图片进行处理。

CameraService.ets文件相机服务构造函数:

constructor(imgReceiver?: image.ImageReceiver) {
    if (imgReceiver === undefined) {
      this.imageReceiver = image.createImageReceiver(QRCodeScanConst.IMG_DEFAULT_SIZE.WIDTH,
      QRCodeScanConst.IMG_DEFAULT_SIZE.HEIGHT, image.ImageFormat.JPEG, QRCodeScanConst.MAX_IMAGE_CAPACITY)
    } else {
      this.imageReceiver = image.createImageReceiver(imgReceiver.size.width, imgReceiver.size.height,
      imgReceiver.format, imgReceiver.capacity)
    }
  }

在 CameraService.ets 文件创建相机函数中,主要包含如下几个步骤:

● 获取支持的相机

根据context获取CameraManager,然后获取支持的相机(摄像头)。如果没有支持的相机,则然后。

如有支持的相机,则默认使用相机列表中的第一个。实际应用中,对于二维码扫描,需要使用后置相机摄像头。

● 获取相机输入输出流

首先,根据指定的相机,创建相机输入流this.cameraInput。

然后,获取相机的cameraOutputCapability参数,接着创建两个输出流:

1、 预览输出流

创建相机预览输出流this.previewOutput,使用的surfaceId来自XComponent组件。预览输出流,对应相机拍照前的图片预览。

2、相片输出流

创建相片输出流this.photoOutput,使用的receivingSurfaceId来自上文创建的图片接收器。相片输出流,用于保存到相片。

● 配置相机会话

配置相机会话,也比较简单,添加输入流和输出流即可,见代码及其注释。

CameraService.ets文件创建相机函数:

/**
 * 创建相机
 */
async createCamera(surfaceId: string) {
  Logger.info("createCamera start")
  // 根据context获取CameraManager
  let cameraManager = camera.getCameraManager(AppStorage.Get('context'))
  // 获取Camera对象数组
  let cameras = cameraManager.getSupportedCameras()
  // 没有相机就停止
  if (cameras.length === 0) {
    Logger.error("createCamera: cameras length is 0.")
    return
  }
  // 拿到相机列表中的第一个默认相机id, 根据id获取相机输入流
  this.cameraInput = cameraManager.createCameraInput(cameras[0])
  this.cameraInput.open()
  // 获取cameraOutputCapability参数
  let cameraOutputCapability = cameraManager.getSupportedOutputCapability(cameras[0])
  // 获取相机输出流
  this.previewOutput = cameraManager.createPreviewOutput(cameraOutputCapability.previewProfiles[0], surfaceId)
  // 获取一个可以创建相片输出流的id
  let receivingSurfaceId = await this.imageReceiver.getReceivingSurfaceId()
  // 创建相片输出流
  this.photoOutput = cameraManager.createPhotoOutput(cameraOutputCapability.photoProfiles[0], receivingSurfaceId)
  // 获取捕获会话的实例
  this.captureSession = cameraManager.createCaptureSession()
  // 开始会话配置
  this.captureSession.beginConfig()
  // 使用相机输入流---添加一个摄像头输入流
  this.captureSession.addInput(this.cameraInput)
  // 使用相机输出流---添加一个摄像头输出
   this.captureSession.addOutput(this.previewOutput)
  // 使用相片输出流---添加相机照片的输出
  this.captureSession.addOutput(this.photoOutput)
  // 结束并提交配置
  await this.captureSession.commitConfig()
  // 开始捕获会话
  await this.captureSession.start()
  Logger.info("createCamera end")
}

CameraService.ets 文件拍照函数中,指定相片参数设置,然后调用capture()函数完成拍照。

拍照后会触发图片接收器的’imageArrival’事件。拍照函数在使用相机扫描二维码的时候调用。

该图片接收器可以监听’imageArrival’事件,当相机拍照时会触发该事件。在监听事件的回调函数里,实现对拍照的图片进行处理。

CameraService.ets文件拍照函数:

takePicture() {
  let photoSetting = {
    rotation: camera.ImageRotation.ROTATION_0,
    quality: camera.QualityLevel.QUALITY_LEVEL_MEDIUM,
    mirror: false
  }
  this.photoOutput.capture(photoSetting)
}

二维码解析实现代码

二维码解析类文件为: QRCodeParser.ets ,支持拍照识别二维码,还支持从相册选择二维码图片进行识别。

我们首先看下如何解析从相机获取的二维码图片,对应函数为:parseQRCodeImageFromCamera,该类指定一个时间随机的图片文件名,图片归档格式,然后继续调用函数parseQRCodeImageWithNameFromCamera。

/**
 * 解析从相机获取的二维码图片
 *
 * @param cameraService
 * @param canvasContext
 */
parseQRCodeImageFromCamera(cameraService: CameraService,
                           imageComponentType?: image.ComponentType): void {
  Logger.info("parseQRCodeImageFromCamera start")
  let fileName = this.getRandomFileName(QRCodeScanConst.IMG_FILE_PREFIX, QRCodeScanConst.IMG_SUFFIX_JPG)
  this.parseQRCodeImageWithNameFromCamera(cameraService, fileName, imageComponentType);
  Logger.info("parseQRCodeImageFromCamera end")
}

在函数parseQRCodeImageWithNameFromCamera中,注册图片接收器监听’imageArrival’事件,在监听函数里,对二维码图片进行解析识别。

当相机对二维码拍照后,二维码图片会被保存到指定的目录下,返回文件URI。保存图片的函数createPublicDirFileAsset的实现,可以自行查阅源码。

根据返回的图片URI,调用函数parseImageQRCode对二维码进行解析。函数parseImageQRCode后文会介绍。

如果解析失败,弹窗提示解析失败。如果解析成功,会被解析结果保存到AppStorage。

保存到AppStorage的二维码解析结果会被@watch装饰器的变量监视,当监视到有二维码识别结果后,会在界面展示,后文会介绍。

QRCodeParser.ets文件parseQRCodeImageWithNameFromCamera函数代码:

/**
  * 解析从相机获取的二维码图片,指定文件名称
  *
  * @param cameraService
  * @param canvasContext
  */
 parseQRCodeImageWithNameFromCamera(cameraService: CameraService,
                                    fileDisplayName: string,
                                    imageComponentType?: image.ComponentType): void {
   Logger.info("parseQRCodeImageWithNameFromCamera...")
   cameraService.imageReceiver.on('imageArrival', async () => {
     Logger.info("parseQRCodeImageWithNameFromCamera imageArrival start")
     // 从接收器获取下一个图像,并返回结果
     let targetImage: image.Image = await cameraService.imageReceiver.readNextImage()
     // 默认按JPEG格式处理
     let imgComponentType = imageComponentType === undefined ? image.ComponentType.JPEG : imageComponentType
     let imageComponent = await targetImage.getComponent(imgComponentType)
     // 将image的ArrayBuffer写入指定文件中,返回文件uri
     let imageUri = await this.createPublicDirFileAsset(fileDisplayName, mediaLibrary.MediaType.IMAGE,
                    mediaLibrary.DirectoryType.DIR_IMAGE, imageComponent.byteBuffer);
     // 释放已读取的image资源,以便处理下一个资源
     await targetImage.release()
 
     // 解析二维码
     let qrCodeParseRlt = await this.parseImageQRCode(imageUri);
     if (!qrCodeParseRlt.isSucess) {
       Logger.error("parseQRCodeImageWithNameFromCamera qrCodeParseRlt is null")
       prompt.showToast({
         message: $r('app.string.qrCodeNotRecognized')
       })
       return;
     }
     // 拼接解析结果
     AppStorage.SetOrCreate(QRCodeScanConst.QR_CODE_PARSE_RESULT, qrCodeParseRlt.decodeResult);
     Logger.info("parseQRCodeImageWithNameFromCamera imageArrival end")
   })
 }

二维码解析类文件为: QRCodeParser.ets ,支持拍照识别二维码,还支持从相册选择二维码图片进行识别。

我们接着,再看下如何解析从相册里挑选的二维码图片。

参数imageSrc为选定图片的URI地址。

getImageSource()代码可以自行查询,实现根据图片URI返回图片的宽、高,以及图片的pixelMap数据。然后,把像素数据写入ArrayBuffer,供zxing二维码识别程序使用。

函数RGBLuminanceSource、BinaryBitmap、BinaryBitmap等都是zxing的类。通过调用MultiFormatReader的decode函数对二维码图像进行解析。

如果解析成功,会返回成功的标记和解析的结果。

如果解析失败,会在catch语句块里进行处理,会返回失败的标记和解析失败的原因。

QRCodeParser.ets文件parseImageQRCode函数代码:

/**
  * 解析图片二维码信息
  * @param canvasContext
  * @param imageSrc
  */
 async parseImageQRCode(imageSrc: string): Promise<DecodeResultAttribute> {
   Logger.info(`parseImageQRCode start`);
   // 获取图片的宽高
   let imageSource = await this.getImageSource(imageSrc);
   let imageWidth = imageSource.width;
   let imageHeight = imageSource.height;
   // 获取PixelMap图片数据
   let pixMapData = imageSource.pixelMap;
   let pixelBytesNumber = pixMapData.getPixelBytesNumber();
   let arrayBuffer: ArrayBuffer = new ArrayBuffer(pixelBytesNumber);
   // 读取图像像素数据,结果写入ArrayBuffer里
   await pixMapData.readPixelsToBuffer(arrayBuffer);
   let int32Array = new Int32Array(arrayBuffer);
   let luminanceSource = new RGBLuminanceSource(int32Array, imageWidth, imageHeight);
   let binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
   let mltiFormatReader = new MultiFormatReader();
   let hints = new Map();
   hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);
   mltiFormatReader.setHints(hints);
   try {
     // 解析二维码
     let decodeResult = mltiFormatReader.decode(binaryBitmap);
     let decodeText = decodeResult.getText();
     Logger.info(`parseImageQRCode end ${decodeText}`);
     return { isSucess: true, decodeResult: decodeText };
   } catch (err) {
     let error = `The error is ${err}`;
     Logger.info(`parseImageQRCode end`);
     return { isSucess: false, decodeResult: error };
   }
 }

相机扫描识别二维码

在文件 QRCodeScanComponent.ets 中实现了二维码扫描自定义组件。我们看下该文件中如何实现相机扫描二维码的。

在二维码扫描组件的aboutToAppear()函数调用的watchCameraPermission()函数,用于使用相机扫描二维码进行识别。

在watchCameraPermission()函数中,使用setInterval函数每100ms判断下是否具有相机权限,当有相机权限的时候,才能使用相机扫描二维码。

当具备相机权限时 ,使用setInterval函数每4000ms轮询判断下是否识别到二维码图片,如果识别到则取消执行轮询。

如果没有识别到二维码,则继续调用函数takePicture()拍照。调用该函数后,会触发图片接收器的监听事件’imageArrival’,对这个事件的监听分析,见上文。

文件QRCodeScanComponent.ets中,相机拍照识别二维码的代码片段:

aboutToAppear() {
// 监听相机权限
this.watchCameraPermission()
// 设置扫描动画
this.setQRCodeScanAnimation()
// 解析二维码图片信息
this.qrCodeParser.parseQRCodeImageFromCamera(this.cameraService);
}
......
// 监听相机权限变化
watchCameraPermission() {
let interval = setInterval(() => {
  this.hasCameraPermission = AppStorage.Get(QRCodeScanConst.HAS_CAMERA_PERMISSION)
  if (this.hasCameraPermission) {
    let qrCodeScanInterval = setInterval(() => {
      if (this.qrCodeParseResult.length > 0 || this.isQRCodeScanStopped) {
        clearInterval(qrCodeScanInterval)
      }
      // 拍照
      this.cameraService.takePicture()
    }, 4000)
    clearInterval(interval)
  }
}, 100)
}

识别相册二维码图片

在文件 QRCodeScanComponent.ets 中实现了二维码扫描自定义组件。我们看下该文件中如何识别相册二维码图片。

首先,设置this.isQRCodeScanStopped为true,这个会关闭相机拍照识别二维码。

然后,通过startAbilityForResult启动相册应用,供用户选择二维码图片。

如果选择图片失败,则弹窗报错。

如果选择图片成功,则调用二维码解码函数parseImageQRCode完成对图片二维码的识别。

如果识别二维码成功,则弹窗展示二维码结果。

如果识别识别,则toast展示:未识别到二维码。

文件QRCodeScanComponent.ets中,相册选择二维码图片进行识别代码片段:

Image($r('app.media.scan_photo'))
  .width(30)
  .height(30)
  .id('scanPhoto')
  .onClick(async () => {
    // 打开相册获取图片
    this.isQRCodeScanStopped = true
    let context = AppStorage.Get('context') as common.UIAbilityContext
    await context.startAbilityForResult({
      parameters: { uri: 'singleselect' },
      bundleName: 'com.ohos.photos',
      abilityName: 'com.ohos.photos.MainAbility',
    }).then(data => {
      // 获取want数据
      let want = data['want'];
      if (want) {
        // param代表want参数中的paramters
        let param = want['parameters'];
        if (param) {
          // 被选中的图片路径media/image/8
          let selectedUri = param['select-item-list'];
          setTimeout(async () => {
            if (!selectedUri) {
              prompt.showToast({
                message: $r('app.string.queryImageFailed'),
                duration: 1000
              })
              return;
            }
            // 获取解析数据
            let qrCodeParseRlt = await this.qrCodeParser.parseImageQRCode(selectedUri[0]);
            if (qrCodeParseRlt.isSucess) {
              prompt.showDialog({
                title: $r('app.string.qrcodeResult'),
                message: qrCodeParseRlt.decodeResult
              })
            } else {
              prompt.showToast({
                message: $r('app.string.qrCodeNotRecognized')
              })
            }
          }, 50)
        }
      }
    })
  })

二维码扫描组件界面

在文件 QRCodeScanComponent.ets 中实现了二维码扫描自定义组件。我们看下二维码扫描组件的页面布局。

整个页面使用Stack进行堆叠布局。

如果有相机权限,会XComponent组件,用于展示相机的预览输出流。XComponent组件的onLoad函数里会创建相机,onDestroy函数里会释放相机。

Image($r(‘app.media.scan_border’))图片就是二维码扫描框,引导用户把二维码放到框内进行扫描识别。

Divider是个分割线,该分割线使能了动画效果,在识别二维码的过程中,分割线从二维码识别框里从上到下移动。扫描动画实现代码如下:

// 扫描扫描动画
setQRCodeScanAnimation() {
  setInterval(() => {
    animateTo({
      duration: 1000, // 动画时间
      tempo: 0.5, // 动画速率
      curve: Curve.EaseInOut,
      delay: 200, // 动画延迟时间
      iterations: -1, // 动画是否重复播放
      playMode: PlayMode.Normal,
    }, () => {
      this.animationOrdinate = 390 // 扫描动画结束Y坐标
    })
  }, 2000)
}

Text($r(‘app.string.putTheQRCodeToScan’))引导用户把二维码放到框内进行扫描识别。

Image($r(‘app.media.scan_back’))返回退出应用。

Image($r(‘app.media.scan_photo’))从相册里挑选二维码图片进行识别。

build() {
Column() {
  Stack() {
    if (this.hasCameraPermission) {
      XComponent({
        id: 'componentId',
        type: 'surface',
        controller: this.xComponentController
      })
        .onLoad(() => {
          // 适配可能需要获取设备信息
          this.xComponentController.setXComponentSurfaceSize({
            surfaceWidth: QRCodeScanConst.IMG_DEFAULT_SIZE.WIDTH,
            surfaceHeight: QRCodeScanConst.IMG_DEFAULT_SIZE.HEIGHT
          })
          this.surFaceId = this.xComponentController.getXComponentSurfaceId()
          this.cameraService.createCamera(this.surFaceId)
        })
        .onDestroy(() => {
          this.cameraService.releaseCamera()
        })
        .height('100%')
        .width('100%')
    }
    Column() {
      Column() {
        Image($r('app.media.scan_border'))
        ......
        Divider()
          .strokeWidth(1)
          .height(4)
          .width('100%')
          .color(Color.White)
          .width('100%')
          .position({ x: 0, y: 0 })
          .translate({ x: 0, y: this.animationOrdinate })
      }
    ......
      Text($r('app.string.putTheQRCodeToScan'))
    ......
    }
    ......
    Row() {
      Image($r('app.media.scan_back'))
      ......
      Row({ space: 16 }) {
        Image($r('app.media.scan_photo'))
        ......
}

运行测试效果

可以下载 橘子购物示例应用 代码,使用DevEco Studio编译构建,使用Simulator模拟器或者真实设备进行运行体验。可以体验下使用相机对二维码图片进行识别,还可以尝试下识别相册中的二维码图片。

git init
git config core.sparsecheckout true
echo code/Solutions/Shopping/OrangeShopping/ > .git/info/sparse-checkout
git remote add origin https://gitee.com/openharmony/applications_app_samples.git
git pull origin master

注意事项

当前二维码示例应用识别相册的二维码,弹出识别结果后,程序会崩溃,已经提单跟踪。示例程序待改进。

使用相机功能直接拍摄二维码的功能,一直没有成功运行,需要进一步优化。

如果想更深入的学习 OpenHarmony (鸿蒙南向)全栈开发的内容,可以参考以下学习文档:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:https://gitee.com/MNxiaona/733GH

在这里插入图片描述

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现C语言调用zbar实现二维码扫码,需要进行以下步骤: 1. 下载zbar的源码,并进行编译安装。 2. 在C程序中引用zbar的头文件,并链接zbar的库文件。 3. 使用zbar提供的API函数实现二维码扫码功能。 以下是一个简单的示例程序: ``` #include <stdio.h> #include <zbar.h> int main() { // 初始化zbar zbar_image_scanner_t *scanner = zbar_image_scanner_create(); zbar_image_scanner_set_config(scanner, 0, ZBAR_CFG_ENABLE, 1); // 读取二维码图片 zbar_image_t *image = zbar_image_create(); zbar_image_set_format(image, *(int*)"Y800"); // 设置图片格式为Y800 FILE *file = fopen("qrcode.jpg", "rb"); fseek(file, 0, SEEK_END); long size = ftell(file); fseek(file, 0, SEEK_SET); char *data = (char*)malloc(size); fread(data, 1, size, file); zbar_image_set_size(image, 640, 480); // 设置图片大小 zbar_image_set_data(image, data, size, zbar_image_free_data); // 扫描二维码 int n = zbar_scan_image(scanner, image); const zbar_symbol_t *symbol = zbar_image_first_symbol(image); for (; symbol; symbol = zbar_symbol_next(symbol)) { const char *data = zbar_symbol_get_data(symbol); printf("QR Code: %s\n", data); } // 释放资源 zbar_image_destroy(image); zbar_image_scanner_destroy(scanner); fclose(file); return 0; } ``` 在这个示例程序中,我们首先初始化了zbar,然后读取了一个名为qrcode.jpg的二维码图片,并设置了图片的格式和大小。接着,我们调用zbar_scan_image函数扫描二维码,并遍历所有扫描到的二维码进行输出。最后,我们释放了所有的资源。 需要注意的是,这个示例程序仅供参考,实际使用时可能需要进行一些修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值