目录

如何将图片转换为Base64格式

问题现象

HarmonyOS如何将图片转成base64格式。

背景知识

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的场景和方法:

对于大尺寸图片,为避免内存溢出,可根据业务需要设置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);
    };
  }
}

常见FAQ

Q:PixelMap通过ImagePacker转换base64后,为什么图片文件会膨胀超过33%甚至几倍?

A:原始图片文件在最初编码时设置的quality参数仅为80甚至更低,使用ImagePacker编码时将quality设为100,就会导致图片文件体积明显增大,详情可参考PixelMap和base64的相互转换。

Q:对于不同路径获取到的ArrayBuffer数据,如何判断是否为空?

A:可以将文件存入沙箱中,将文件拖进notepad中,查看其二进制数据,除去头尾之外,如果大部分均为0数据,说明该ArrayBuffer为一个空Buffer。