• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import fileio from '@ohos.fileio';
17import image from '@ohos.multimedia.image';
18import photoAccessHelper from '@ohos.file.photoAccessHelper';
19import common from '@ohos.app.ability.common';
20import prompt from '@ohos.promptAction';
21import {
22  BarcodeFormat,
23  MultiFormatWriter,
24  BitMatrix,
25  ZXingStringEncoding,
26  EncodeHintType,
27  MultiFormatReader,
28  DecodeHintType,
29  RGBLuminanceSource,
30  BinaryBitmap,
31  HybridBinarizer
32} from '@ohos/zxing';
33import DateTimeUtil from '../utils/DateTimeUtil';
34import { CameraService } from './CameraService';
35import { QRCodeScanConst, ImageAttribute, DecodeResultAttribute } from './QRCodeScanConst';
36import Logger from '../utils/Logger';
37import dataSharePredicates from '@ohos.data.dataSharePredicates';
38import deviceInfo from '@ohos.deviceInfo';
39
40const TAG: string = 'QRCodeParser';
41
42/**
43 * 二维码解析器
44 */
45class QRCodeParser {
46  private deviceType: string = 'default';
47
48  constructor() {
49    this.deviceType = deviceInfo.deviceType;
50    Logger.info(TAG, `deviceType: ${this.deviceType}`);
51  }
52
53  /**
54   * 解析从相机获取的二维码图片
55   *
56   * @param cameraService
57   * @param canvasContext
58   */
59  parseQRCodeImageFromCamera(cameraService: CameraService,
60                             imageComponentType?: image.ComponentType): void {
61    Logger.info("parseQRCodeImageFromCamera start")
62    this.parseQRCodeImageWithNameFromCamera(cameraService, imageComponentType);
63    Logger.info("parseQRCodeImageFromCamera end")
64  }
65
66  /**
67   * 解析从相机获取的二维码图片,指定文件名称
68   *
69   * @param cameraService
70   * @param canvasContext
71   */
72  parseQRCodeImageWithNameFromCamera(cameraService: CameraService,
73                                     imageComponentType?: image.ComponentType): void {
74    Logger.info("parseQRCodeImageWithNameFromCamera...")
75    cameraService.imageReceiver.on('imageArrival', async () => {
76      Logger.info("parseQRCodeImageWithNameFromCamera imageArrival start")
77      // 从接收器获取下一个图像,并返回结果
78      let targetImage: image.Image = await cameraService.imageReceiver.readNextImage()
79      // 默认按JPEG格式处理
80      let imgComponentType = imageComponentType === undefined ? image.ComponentType.JPEG : imageComponentType
81      let imageComponent = await targetImage.getComponent(imgComponentType)
82      // 给每次接收的image资源赋予随机文件名
83      let fileName = this.getRandomFileName(QRCodeScanConst.IMG_FILE_PREFIX, QRCodeScanConst.IMG_SUFFIX_JPG)
84      // 将image的ArrayBuffer写入指定文件中,返回文件uri
85      let imageUri = await this.createPublicDirFileAsset(fileName, imageComponent.byteBuffer);
86      // 释放已读取的image资源,以便处理下一个资源
87      await targetImage.release()
88
89      // 解析二维码
90      let qrCodeParseRlt = await this.parseImageQRCode(imageUri);
91      if (!qrCodeParseRlt.isSucess) {
92        Logger.error("parseQRCodeImageWithNameFromCamera qrCodeParseRlt is null")
93        prompt.showToast({
94          message: $r('app.string.qrCodeNotRecognized')
95        })
96        return;
97      }
98      // 拼接解析结果
99      AppStorage.SetOrCreate(QRCodeScanConst.QR_CODE_PARSE_RESULT, qrCodeParseRlt.decodeResult);
100      Logger.info("parseQRCodeImageWithNameFromCamera imageArrival end")
101    })
102  }
103
104  /**
105   * 解析图片二维码信息
106   * @param canvasContext
107   * @param imageSrc
108   */
109  async parseImageQRCode(imageSrc: string): Promise<DecodeResultAttribute> {
110    Logger.info(TAG, `parseImageQRCode start`);
111    let parseResult: DecodeResultAttribute = {
112      isSucess: false,
113      decodeResult: 'failed'
114    };
115    let width: number;
116    let height: number;
117    let pixelBytesNumber: number;
118    let arrayBuffer: ArrayBuffer;
119    if (this.deviceType !== 'default') {
120      let imageSource: ImageAttribute = await this.getImageSource(imageSrc);
121      imageSource = await this.getImageSource(imageSrc);
122      // 图片宽高
123      width = imageSource.width;
124      height = imageSource.height;
125      pixelBytesNumber = imageSource.pixelMap.getPixelBytesNumber();
126      arrayBuffer = new ArrayBuffer(pixelBytesNumber);
127      // 读取图像像素数据,结果写入ArrayBuffer里
128      await imageSource.pixelMap.readPixelsToBuffer(arrayBuffer);
129    } else {
130      let media = photoAccessHelper.getPhotoAccessHelper(AppStorage.get<common.UIAbilityContext>('context'));
131      let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
132      let imagesIdFetchOp: photoAccessHelper.FetchOptions = {
133        fetchColumns: [],
134        predicates: predicates.equalTo('uri', imageSrc)
135      };
136      // 获取图片文件资源
137      let fetchIdFileResult = await media.getAssets(imagesIdFetchOp);
138      let fileIdAsset = await fetchIdFileResult.getFirstObject();
139      // 获取文件描述符
140      let fd = await fileIdAsset.open('RW');
141      // 获取PixelMap图片数据
142      let imageSource = image.createImageSource(fd, { sourceDensity: 120, sourceSize: { width: 120, height: 120 } });
143      let decodingOptions: image.DecodingOptions = {
144        sampleSize: 1,
145        editable: true,
146        desiredSize: { width: 120, height: 120 },
147        rotate: 0,
148        desiredPixelFormat: 3,
149        desiredRegion: { size: { height: 120, width: 120 }, x: 0, y: 0 },
150        index: 0
151      };
152      // 图片宽高
153      width = 120;
154      height = 120;
155      let pixMapData = await imageSource.createPixelMap(decodingOptions);
156      pixelBytesNumber = pixMapData.getPixelBytesNumber();
157      arrayBuffer = new ArrayBuffer(pixelBytesNumber);
158      // 读取图像像素数据,结果写入ArrayBuffer里
159      await pixMapData.readPixelsToBuffer(arrayBuffer);
160    }
161    let int32Array = new Int32Array(arrayBuffer);
162    let luminanceSource = new RGBLuminanceSource(int32Array, width, height);
163    let binaryBitmap = new BinaryBitmap(new HybridBinarizer(luminanceSource));
164    let mltiFormatReader = new MultiFormatReader();
165    let hints: Map<number, number[]> = new Map();
166    hints.set(DecodeHintType.POSSIBLE_FORMATS, [BarcodeFormat.QR_CODE]);
167    mltiFormatReader.setHints(hints);
168    try {
169      // 解析二维码
170      let decodeResult = mltiFormatReader.decode(binaryBitmap);
171      let decodeText = decodeResult.getText();
172      Logger.info(TAG, `parseImageQRCode end ${decodeText}`);
173      parseResult = { isSucess: true, decodeResult: decodeText };
174    } catch (err) {
175      let error = `The error is ${err}`;
176      Logger.info(TAG, `parseImageQRCode end`);
177      parseResult = { isSucess: false, decodeResult: error };
178    }
179    return parseResult;
180  }
181
182  /**
183   * 获取图片的属性
184   * @param context
185   * @param imageSrc
186   */
187  async getImageSource(imageSrc: string): Promise<ImageAttribute> {
188    Logger.info(`getImageSource start`);
189    let media = photoAccessHelper.getPhotoAccessHelper(AppStorage.get<common.UIAbilityContext>('context'));
190    let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
191    let imagesIdFetchOp: photoAccessHelper.FetchOptions = {
192      fetchColumns: ['width', 'height'],
193      predicates: predicates.equalTo('uri', imageSrc)
194    };
195    // 获取图片文件资源
196    let fetchIdFileResult = await media.getAssets(imagesIdFetchOp);
197    let fileIdAsset = await fetchIdFileResult.getFirstObject();
198    // 将字符串分割,下标为1的数据即为图片类型
199    let imgType = fileIdAsset.displayName.split('.')[1];
200    // 获取文件描述符
201    let fd = await fileIdAsset.open('RW');
202    let context = AppStorage.get<common.UIAbilityContext>('context');
203    // 获取当前时间
204    let time = new Date().getTime();
205    // 拼接路径
206    let imagePath = `${context?.cacheDir}/${time.toString()}_note.${imgType}`;
207    // 将图片copy到此路径当中
208    await fileio.copyFile(fd, imagePath);
209    // 创建一个图片源类
210    let imageSource = image.createImageSource(imagePath);
211    // 创建PixelMap数组
212    let pixelMap = await imageSource.createPixelMap();
213    // 4、关闭安fd,Asset
214    await fileIdAsset.close(fd);
215    return {
216      width: fileIdAsset.get('width') as number,
217      height: fileIdAsset.get('height') as number,
218      pixelMap: pixelMap
219    };
220  }
221
222  /**
223   * 在媒体公共资源目录下的创建指定类型的资源对象
224   */
225  async createPublicDirFileAsset(fileDisplayName: string,
226                                 fileByteBuffer: ArrayBuffer): Promise<string> {
227    Logger.info("createPublicDirFileAsset start")
228    // 获取mediaLibrary对象
229    let mediaLibraryObj = photoAccessHelper.getPhotoAccessHelper(AppStorage.get('context'))
230    let fileAsset = await mediaLibraryObj.createAsset(fileDisplayName)
231    // 拿到fileAccess资源对应的uri
232    let fileUri = fileAsset.uri
233    // 调用open方法打开这个资源对象 // TODO 为啥要打开?
234    let fd = await fileAsset.open('Rw')
235    // 将图片资源写入对应的fileAccess
236    await fileio.write(fd, fileByteBuffer)
237    // 关闭这个资源对象
238    await fileAsset.close(fd)
239    Logger.info("createPublicDirFileAsset end")
240    return fileUri
241  }
242
243  /**
244   * 根据时间生成随机文件名
245   *
246   * @param filePrefix 文件名前缀
247   * @param fileSuffix 文件名后缀
248   */
249  getRandomFileName(filePrefix: string, fileSuffix: string) {
250    let dateTimeUtil = new DateTimeUtil()
251    return filePrefix + `${dateTimeUtil.getDate()}_${dateTimeUtil.getTime()}` + fileSuffix
252  }
253}
254
255export { QRCodeParser }