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 }