HarmonyOS鸿蒙Next中如何将Canvas绘制的资源以图片的形式保存至图库

HarmonyOS鸿蒙Next中如何将Canvas绘制的资源以图片的形式保存至图库 1、自定义绘制的Canvas如何转为图片?

2、图片保存方案种类繁杂,申请各种权限成为您前进路上的绊脚石?

3 回复

无需申请任何权限,即可完成。

实现步骤:

  1. 将Canvas组件赋予一个id,通过组件id截图的形式获取到对应的pixelMap;
  2. 通过SaveButton安全控件将pixelMap通过imagePackerApi.packToFile()这个API直接编写进文件即可。
  • ImagePacker:图片编码器类,用于图片压缩和编码。在调用ImagePacker的方法前,需要先通过createImagePacker构建一个ImagePacker实例。
  • SaveButton:安全控件的保存控件。应用集成保存控件后,用户首次使用保存控件展示弹窗,在点击允许后自动授权,应用会在短时间内获取访问媒体库特权接口的授权。后续使用无需弹窗授权。在API version 19及之前的版本中,授权持续时间为10秒;在API version20及之后的版本中,授权持续时间为1分钟。

真机效果:

关键代码:

1、id截图的形式获取到对应的pixelMap

  @Local pixelMap?: image.PixelMap
  private cvsID: string = 'canvasId'
  //...  
  this.getUIContext().getComponentSnapshot().get(this.cvsID, (error: Error, pixmap: image.PixelMap) => {

  })

2、imagePackerApi.packToFile()将pixelMap编写进图库

imagePackerApi.packToFile(this.pixelMap, file.fd, packOpts, (err: BusinessError) => {
      if (err) {
      } else {
        imagePackerApi.release((err: BusinessError) => {
          if (err) {
          } else {
            fileIo.close(file.fd);
          }
        })
        promptAction.showToast({ message: '已保存至相册!' });
      }
    })

完整代码:

import { image } from "@kit.ImageKit";
import { common } from "@kit.AbilityKit";
import { photoAccessHelper } from "@kit.MediaLibraryKit";
import { fileIo } from "@kit.CoreFileKit";
import { promptAction } from "@kit.ArkUI";
import { BusinessError } from '@kit.BasicServicesKit';


@ComponentV2
export struct CanvasSaveImg {
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  @Local pixelMap?: image.PixelMap
  private cvsID: string = 'canvasId'
  private context: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;

  build() {
    Scroll() {
      Column() {
        Canvas(this.ctx)
          .width(200)
          .height(200)
          .backgroundColor('#F5DC62')
          .onReady(() => {
            // 设定填充样式,填充颜色设为蓝色
            this.ctx.fillStyle = '#ff7f27'
            // 以(50, 50)为左上顶点,画一个宽高400的矩形B
            this.ctx.fillRect(10, 10, 190, 190)
            this.ctx.fillStyle = '#22b14c';
            // 以(50, 50)为左上顶点,画一个宽高200的矩形A
            this.ctx.fillRect(10, 10, 80, 80);
          })
          .id(this.cvsID)
        Blank().height(30)
        SaveButton({ text: SaveDescription.SAVE_IMAGE }).width('80%')
          .onClick((event: ClickEvent, result: SaveButtonOnClickResult) => {
            if (result === SaveButtonOnClickResult.SUCCESS) {
              // 免去权限申请和权限请求等环节,获得临时授权,保存对应图片。
              this.getUIContext().getComponentSnapshot().get(this.cvsID, (error: Error, pixmap: image.PixelMap) => {
                this.pixelMap = pixmap;
                this.savePixelMap();
              });
            } else {
              promptAction.openToast({ message: '设置权限失败!' });
            }
          })
      }.safeAreaPadding({ top: 30 })
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
  }

  async savePixelMap() {
    // 获取相册的保存路径
    let helper = photoAccessHelper.getPhotoAccessHelper(this.context);
    let uri = await helper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpeg');
    let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
    let imagePackerApi = image.createImagePacker();
    let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 98 };

    imagePackerApi.packToFile(this.pixelMap, file.fd, packOpts, (err: BusinessError) => {
      if (err) {
        console.error(`Failed to pack the image to file.code ${err.code},message is ${err.message}`);
      } else {
        console.info('Succeeded in packing the image to file.');
        imagePackerApi.release((err: BusinessError) => {
          if (err) {
            console.error(`Failed to release the image source instance.code ${err.code},message is ${err.message}`);
          } else {
            console.info('Succeeded in releasing the image source instance.');
            fileIo.close(file.fd);
          }
        })
        promptAction.showToast({ message: '已保存至相册!' });
      }
    })
  }
}

组件引用

@Entry
@ComponentV2
struct Index {
  build() {
    Column() {
      CanvasSaveImg()
    }
    .height('100%')
    .width('100%')
  }
}

相关文档:

更多关于HarmonyOS鸿蒙Next中如何将Canvas绘制的资源以图片的形式保存至图库的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,使用Canvas绘制内容后,可通过PixelMapImagePacker将绘制结果转换为图片数据。使用@ohos.file.fs@ohos.file.photoAccessHelper模块,将图片数据写入应用沙箱路径,再通过PhotoAccessHelper接口将文件插入系统图库,实现保存。

在HarmonyOS Next中,将Canvas绘制内容保存为图片并存入图库,核心流程分为两步:Canvas转图片图片写入媒体库。下面提供基于ArkTS的简洁实现方案。

1. Canvas转图片

使用Canvas组件的toDataURL方法,可将当前绘制内容转换为Base64格式的图片数据。

import { drawing } from '@kit.ArkGraphics2D';

// 假设您的Canvas组件已通过ref绑定
@State canvasRef: CanvasRenderingContext2D | null = null;

// 在绘制完成后调用
const convertCanvasToImage = (): string => {
  if (!this.canvasRef) {
    return '';
  }
  // 生成Base64格式的图片数据(默认为PNG)
  const dataURL = this.canvasRef.toDataURL();
  return dataURL; // 格式为:"data:image/png;base64,iVBORw0KGgo..."
}

2. 保存图片至图库

需使用媒体库管理模块(@kit.MediaLibraryKit)将图片数据写入公共目录。关键步骤包括:

a. 申请必要权限module.json5中声明以下权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:reason_desc" // 填写您的描述
      },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO",
        "reason": "$string:reason_desc"
      }
    ]
  }
}

注意:HarmonyOS Next的权限模型需同步在应用配置中明确声明,并在运行时通过abilityAccessCtrl发起动态授权请求。

b. 写入媒体库

import { mediaLibrary } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';

async saveImageToGallery(base64Data: string) {
  // 1. 获取媒体库实例
  const media = mediaLibrary.getMediaLibrary();

  // 2. 创建公共图片目录(如果不存在)
  const publicDir = await media.getPublicDirectory(mediaLibrary.DirectoryType.DIR_IMAGE);

  // 3. 生成唯一文件名
  const fileName = `canvas_${Date.now()}.png`;
  const filePath = `${publicDir}/${fileName}`;

  // 4. 解码Base64并写入文件
  const buffer = this.base64ToArrayBuffer(base64Data.split(',')[1]); // 去除DataURL前缀
  await fileIo.writeFile(filePath, buffer);

  // 5. 通知媒体库扫描新文件(可选,使图片立即在图库中可见)
  await mediaLibrary.scanFile(filePath);
}

// Base64转ArrayBuffer工具方法
private base64ToArrayBuffer(base64: string): ArrayBuffer {
  const binaryString = atob(base64);
  const len = binaryString.length;
  const bytes = new Uint8Array(len);
  for (let i = 0; i < len; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes.buffer;
}

完整调用示例

// 在保存按钮事件中整合流程
async onSaveButtonClick() {
  // 转换Canvas
  const imageData = this.convertCanvasToImage();
  if (!imageData) {
    return;
  }

  // 保存至图库
  await this.saveImageToGallery(imageData);
}

关键说明

  • 权限申请:HarmonyOS Next要求显式声明并动态申请媒体读写权限,这是写入公共图库的必要条件。
  • 文件路径:必须使用getPublicDirectory获取公共目录,私有目录文件无法被图库应用访问。
  • 性能考虑:大尺寸Canvas转换时,建议在异步任务中处理编码与写入操作,避免阻塞UI。

此方案直接使用HarmonyOS Next标准API,避免了依赖第三方库的复杂度。实际开发中需根据您的Canvas实现上下文调整引用获取方式。

回到顶部