base64编码是基于64个可打印字符来表示任意二进制数据的方法,它通常用于在文本协议和文件格式中传输或存储二进制数据。使用base64编码的优缺点如下所示:
优点:
缺点:
HarmonyOS提供Base64Helper工具函数encodeToStringSync方法将Uint8Array转换为base64编码,使用方法如下:
// 入参为Uint8Array function uint8Array2Base64(uint8Array: Uint8Array): string { let base64Helper = new util.Base64Helper(); return base64Helper.encodeToStringSync(uint8Array); } // 入参为ArrayBuffer,可转换为Uint8Array后再转base64 function arrayBuffer2Base64(arrayBuffer: ArrayBuffer): string { return uint8Array2Base64(new Uint8Array(arrayBuffer)); }
下面列举常用的图片转换ArrayBuffer或Uint8Array的场景和方法:
// 开发者根据自身需求传入resource目录下的文件 function readResource2Uint8Array(resource: Resource): Uint8Array { let resourceManager = uContext.getHostContext()?.resourceManager; return resourceManager!.getMediaContentSync(resource.id); }
// 传入应用沙箱路径,需要保证路径存在,路径不存在会使应用崩溃 function readSandFile2ArrayBuffer(sandFilePath: string): ArrayBuffer { let sandFile: fs.File | null = null; try { console.info('sandFilePath:', sandFilePath); let stat = fs.statSync(sandFilePath); let sandFile = fs.openSync(sandFilePath, fs.OpenMode.READ_ONLY); let arrayBuffer = new ArrayBuffer(stat.size); fs.readSync(sandFile.fd, arrayBuffer); return arrayBuffer; } catch (err) { console.error(`readSandFile2ArrayBuffer failed: ${err.code}, message: ${err.message}`); return new ArrayBuffer(0); } finally { if (sandFile) { fs.closeSync(sandFile); } } }
// 调用相册管理模块选择指定图片 async function readUserPhoto2ArrayBuffer(): Promise<ArrayBuffer> { let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 1; let photoPicker = new photoAccessHelper.PhotoViewPicker(); let photoSelectResult = await photoPicker.select(photoSelectOptions); let file: fs.File | null = null; try { let file = fs.openSync(photoSelectResult.photoUris[0], fs.OpenMode.READ_ONLY); let stat = fs.statSync(file.fd); let arrayBuffer = new ArrayBuffer(stat.size); fs.readSync(file.fd, arrayBuffer); return arrayBuffer; } catch (err) { console.error(`readUserPhoto2ArrayBuffer failed: ${err.code}, message: ${err.message}`); return new ArrayBuffer(0); } finally { if (file) { fs.closeSync(file); } } }
// 使用网络图片需要确保module.json5中申请了网络权限ohos.permission.INTERNET,网络图片地址url需要实际可用 async function readHttpFile2ArrayBuffer(url: string): Promise<ArrayBuffer> { try { let httpResponse: http.HttpResponse = await http.createHttp().request(url); if (http.ResponseCode.OK === httpResponse.responseCode) { console.info('httpResponse success'); return httpResponse.result as ArrayBuffer; } } catch (e) { console.error(`http request failed with. Code: ${e.code}, message: ${e.message}`); } return new ArrayBuffer(0); }
// 使用packToData获取PixelMap的ArrayBuffer async function readPixelMap2ArrayBuffer(pixelMap: image.PixelMap, packOpts: image.PackingOption): Promise<ArrayBuffer> { try { let imagePackerApi: image.ImagePacker = image.createImagePacker(); return await imagePackerApi.packToData(pixelMap, packOpts); } catch (e) { console.error(`Failed to pack the image.code ${e.code},message is ${e.message}`); } return new ArrayBuffer(0); }
对于大尺寸图片,为避免内存溢出,可根据业务需要设置PackingOption压缩参数quality,详情可参考quality参数与图片原始大小、压缩后大小的关系。
完整示例参考如下:
import { util } from '@kit.ArkTS'; import fs from '@ohos.file.fs'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { http } from '@kit.NetworkKit'; import { image } from '@kit.ImageKit'; import { UIContext } from '@kit.ArkUI'; let uContext: UIContext; // 入参为Uint8Array function uint8Array2Base64(uint8Array: Uint8Array): string { let base64Helper = new util.Base64Helper(); return base64Helper.encodeToStringSync(uint8Array); } // 入参为ArrayBuffer,可转换为Uint8Array后再转base64 function arrayBuffer2Base64(arrayBuffer: ArrayBuffer): string { return uint8Array2Base64(new Uint8Array(arrayBuffer)); } // 开发者根据自身需求传入resource目录下的文件 function readResource2Uint8Array(resource: Resource): Uint8Array { let resourceManager = uContext.getHostContext()?.resourceManager; return resourceManager!.getMediaContentSync(resource.id); } // 传入应用沙箱路径,需要保证路径存在,路径不存在会使应用崩溃 function readSandFile2ArrayBuffer(sandFilePath: string): ArrayBuffer { let sandFile: fs.File | null = null; try { console.info('sandFilePath:', sandFilePath); let stat = fs.statSync(sandFilePath); let sandFile = fs.openSync(sandFilePath, fs.OpenMode.READ_ONLY); let arrayBuffer = new ArrayBuffer(stat.size); fs.readSync(sandFile.fd, arrayBuffer); return arrayBuffer; } catch (err) { console.error(`readSandFile2ArrayBuffer failed: ${err.code}, message: ${err.message}`); return new ArrayBuffer(0); } finally { if (sandFile) { fs.closeSync(sandFile); } } } // 调用相册管理模块选择指定图片 async function readUserPhoto2ArrayBuffer(): Promise<ArrayBuffer> { let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions(); photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 1; let photoPicker = new photoAccessHelper.PhotoViewPicker(); let photoSelectResult = await photoPicker.select(photoSelectOptions); let file: fs.File | null = null; try { let file = fs.openSync(photoSelectResult.photoUris[0], fs.OpenMode.READ_ONLY); let stat = fs.statSync(file.fd); let arrayBuffer = new ArrayBuffer(stat.size); fs.readSync(file.fd, arrayBuffer); return arrayBuffer; } catch (err) { console.error(`readUserPhoto2ArrayBuffer failed: ${err.code}, message: ${err.message}`); return new ArrayBuffer(0); } finally { if (file) { fs.closeSync(file); } } } // 使用网络图片需要确保module.json5中申请了网络权限ohos.permission.INTERNET,网络图片地址url需要实际可用 async function readHttpFile2ArrayBuffer(url: string): Promise<ArrayBuffer> { try { let httpResponse: http.HttpResponse = await http.createHttp().request(url); if (http.ResponseCode.OK === httpResponse.responseCode) { console.info('httpResponse success'); return httpResponse.result as ArrayBuffer; } } catch (e) { console.error(`http request failed with. Code: ${e.code}, message: ${e.message}`); } return new ArrayBuffer(0); } // 使用packToData获取PixelMap的ArrayBuffer async function readPixelMap2ArrayBuffer(pixelMap: image.PixelMap, packOpts: image.PackingOption): Promise<ArrayBuffer> { try { let imagePackerApi: image.ImagePacker = image.createImagePacker(); return await imagePackerApi.packToData(pixelMap, packOpts); } catch (e) { console.error(`Failed to pack the image.code ${e.code},message is ${e.message}`); } return new ArrayBuffer(0); } @Entry @Component struct Base64 { @State base64Pixel: image.PixelMap | undefined = undefined; @State base64String: string = ''; private url: string = ''; aboutToAppear() { uContext = this.getUIContext(); } build() { Column() { Image('data:image/jpeg;base64,' + this.base64String) .objectFit(ImageFit.Contain) .height('20%'); Text(this.base64String) .width('100%') .height('50%'); Button('本地图片转base64') .onClick(() => { let uint8Array = readResource2Uint8Array($r('app.media.startIcon')); this.base64String = uint8Array2Base64(uint8Array); }); Button('沙箱图片转base64') .onClick(() => { const resourceManager = uContext.getHostContext()?.resourceManager; const imageBuffer = resourceManager!.getMediaContentSync($r('app.media.sandbox').id); const pathDir = uContext.getHostContext()?.filesDir; let filePath = pathDir + '/img.png'; let file: fs.File | null = null; try { let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); let writeLen = fs.writeSync(file.fd, imageBuffer.buffer); let sandArrayBuffer = readSandFile2ArrayBuffer(filePath); this.base64String = arrayBuffer2Base64(sandArrayBuffer); console.info(`${writeLen}`); } catch (err) { console.error(`change to base64 failed: ${err.code}, message: ${err.message}`); } finally { if (file) { fs.closeSync(file); } } }) .margin(5); Button('相册图片转base64') .onClick(async () => { readUserPhoto2ArrayBuffer().then((photoArrayBuffer) => { this.base64String = arrayBuffer2Base64(photoArrayBuffer); console.info('', this.base64String); }); }) .margin(5); Button('网络图片转base64') .onClick(async () => { readHttpFile2ArrayBuffer(this.url).then((httpArrayBuffer) => { this.base64String = arrayBuffer2Base64(httpArrayBuffer); }); }) .margin(5); Button('PixelMap转base64') .onClick(async () => { let resourceManager = uContext.getHostContext()?.resourceManager; resourceManager?.getMediaContent($r('app.media.pixelmap').id).then((value) => { let pixelBuffer = value.buffer as Object as ArrayBuffer; let imageSource = image.createImageSource(pixelBuffer); let opts: image.DecodingOptions = { editable: true }; this.base64Pixel = imageSource.createPixelMapSync(opts); let packingOpts: image.PackingOption = { quality: 100, format: 'image/png' }; readPixelMap2ArrayBuffer(this.base64Pixel, packingOpts).then((pixelBuffer) => { this.base64String = arrayBuffer2Base64(pixelBuffer); }); }); }) .margin(5); }; } }
Q:PixelMap通过ImagePacker转换base64后,为什么图片文件会膨胀超过33%甚至几倍?
A:原始图片文件在最初编码时设置的quality参数仅为80甚至更低,使用ImagePacker编码时将quality设为100,就会导致图片文件体积明显增大,详情可参考PixelMap和base64的相互转换。
Q:对于不同路径获取到的ArrayBuffer数据,如何判断是否为空?
A:可以将文件存入沙箱中,将文件拖进notepad中,查看其二进制数据,除去头尾之外,如果大部分均为0数据,说明该ArrayBuffer为一个空Buffer。
