HarmonyOS鸿蒙Next中Canvas绘图能否导出为PNG或JPEG文件?如何保存到相册?

HarmonyOS鸿蒙Next中Canvas绘图能否导出为PNG或JPEG文件?如何保存到相册? 在我的绘画 App 中创作完成后,希望保存作品到相册。Canvas 内容怎么转成图片文件?

9 回复
  1. 使用OffscreenCanvas创建一个屏幕外渲染的画布,获取OffscreenCanvas组件的绘图上下文,在离屏画布中绘制图案;
  2. 通过getPixelMap获取指定区域内的像素创建PixelMap对象;
  3. 使用ImagePacker中的packToData方法,将像素重新编码为png格式,确保没有绘制的区域为透明;
  4. 通过文件管理接口,创建并打开新的文件,将png图片数据写入到文件中,关闭文件。
  5. 使用 showAssetsCreationDialog 获取相册保存权限的URI,再将沙箱图片写入相册。
  6. 实现代码如下:
import { image } from '@kit.ImageKit';
import { fileIo, fileUri, ReadOptions, WriteOptions } from '@kit.CoreFileKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';

@Entry
@Component
export struct Index {
  @State message: string = 'hello world!'
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  private offCanvas: OffscreenCanvas = new OffscreenCanvas(300, 300)
  private offContext?: OffscreenCanvasRenderingContext2D

  build() {
    Column() {
      Canvas(this.context)
        .height(300)
        .width('100%')
        .onReady(() => {
          this.offContext = this.offCanvas.getContext('2d', this.settings)
          this.offContext.fillStyle = 'rgb(255,0,0)';
          this.offContext.fillRect(0, 0, this.context.width, this.context.height);
          this.offContext.fillStyle = 'rgb(255,255,255)';
          this.offContext.font = '60px sans-serif'
          this.offContext.textAlign = 'start'
          this.offContext.textBaseline = 'top'
          this.offContext.fillText(this.message, 100, 100)
          this.context.transferFromImageBitmap(this.offCanvas.transferToImageBitmap())
        })


      Button('保存图片')
        .onClick(() => {
          this.onSave()
        })
    }
    .alignItems(HorizontalAlign.Center)
  }

  async onSave() {
    if (this.offContext) {
      const pixelMap: image.PixelMap =
        this.offContext.getPixelMap(0, 0, 300, 300)

      const imagePackerApi = image.createImagePacker();
      const buffer = await imagePackerApi.packToData(pixelMap, { format: 'image/png', quality: 100 })
      const filePath = this.getUIContext().getHostContext()?.filesDir + `/${(new Date).getTime()}.png`
      try {
        const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
        fileIo.writeSync(file.fd, buffer)
        fileIo.closeSync(file.fd)

        const config: photoAccessHelper.PhotoCreationConfig[] = []
        config.push({
          fileNameExtension: 'png',
          photoType: photoAccessHelper.PhotoType.IMAGE,
        })

        const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.getUIContext().getHostContext())
        const srcUri = [fileUri.getUriFromPath(filePath)]
        const desUris = await phAccessHelper.showAssetsCreationDialog(srcUri, config)
        this.copyToPhoto(filePath, desUris[0])

      } catch (error) {
        console.error(JSON.stringify(error))
      }
    }
  }

  // 保存到指定路径
  copyToPhoto(srcFilePath: string, destFilePath: string) {
    const srcFile = fileIo.openSync(srcFilePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
    const destFile = fileIo.openSync(destFilePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
    // 读取源文件内容并写入至目的文件
    const stat = fileIo.statSync(srcFilePath)
    const bufSize = stat.size;
    let readSize = 0;
    const buf = new ArrayBuffer(bufSize);
    const readOptions: ReadOptions = {
      offset: readSize,
      length: bufSize
    };
    let readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
    while (readLen > 0) {
      readSize += readLen;
      let writeOptions: WriteOptions = {
        length: readLen
      };
      fileIo.writeSync(destFile.fd, buf, writeOptions);
      readOptions.offset = readSize;
      readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
    }
    // 关闭文件
    fileIo.closeSync(srcFile);
    fileIo.closeSync(destFile);
  }
}

更多关于HarmonyOS鸿蒙Next中Canvas绘图能否导出为PNG或JPEG文件?如何保存到相册?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


实测,你的代码可以跑,

背景知识:

方法一:

直接 Canvas 的绘制方法,将 OffscreenCanvasRenderingContext2D 对象生成一个 PixelMap 对象,将其对象存储到相册中。

方法二:

还可以使用 组件截图 通过 componentSnapshot.get(“root”, (error: Error, pixmap: image.PixelMap) =>{})来获取一个 PixelMap 对象

问题解决:

代码实例:

import { thermal } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { AppUtils } from '../utils/AppUtils';
import { common } from '@kit.AbilityKit';
import { componentSnapshot } from '@kit.ArkUI';


@Entry
@Component
struct CommonPage {
    @State message: string = 'Hello componentSnapshot';
    @State message2: string = 'development';
    private baseContext = this.getUIContext().getHostContext() as common.UIAbilityContext
    timeoutIndex: number = 0
    private settings: RenderingContextSettings = new RenderingContextSettings(true)
    private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
    private offCanvas: OffscreenCanvas = new OffscreenCanvas(300, 300)
    private offContext?: OffscreenCanvasRenderingContext2D

    private snapshotPixmap:image.PixelMap | undefined


    build() {
        Column() {
            //使用 Canvas绘制一个图片
            Canvas(this.context)
                .width(300)
                .height(300)
                .backgroundColor('#ffff00')
                .onReady(() => {
                    this.offContext = this.offCanvas.getContext('2d', this.settings)
                    //设置一个区域
                    this.offContext.fillRect(0, 0, this.context.width, this.context.height)
                    //设置背景颜色
                    this.offContext.fillStyle = "#F0F0F0"
                    // ctx.font = 'font-style font-weight font-size font-family'
                    this.offContext.font = "18vp"
                    this.offContext.textAlign = "start"
                    this.offContext.textBaseline = "middle"
                    this.offContext.fillText(this.message, 100, 100)
                    this.offContext.fillText(this.message2, 100, 200)
                    let image = this.offCanvas.transferToImageBitmap()
                    this.context.transferFromImageBitmap(image)

                }).id("myCanvas")

            //方法一: 使用offContext的PixelMap
            Button("保存 Canvas")
                .fontSize(20)
                .fontColor(Color.White)
                .fontWeight(FontWeight.Bold)
                .onClick(() => {
                    const image = this.offContext?.getPixelMap(0,0,300,300)
                    AppUtils.savePixelMapToAlbum(this.baseContext,image!!)

                })

            方法二:使用componentSnapshot来获取
            Button("保存 componentSnapshot")
                .fontSize(20)
                .fontColor(Color.White)
                .fontWeight(FontWeight.Bold)
                .onClick(() => {
                    //注意:这里的id必须添加
                    componentSnapshot.get("myCanvas", (error: Error, pixmap: image.PixelMap) => {
                        if (error) {
                            console.error("error: " + JSON.stringify(error))
                            return;
                        }
                        this.snapshotPixmap = pixmap
                        AppUtils.savePixelMapToAlbum(this.baseContext,this.snapshotPixmap)
                    }, { scale: 2, waitUntilRenderFinished: true })
                })

        }
        .height('100%')
        .width('100%')
    }
}



//工具类:
import { image } from "@kit.ImageKit";
import { bundleManager, common } from "@kit.AbilityKit";
import { photoAccessHelper } from "@kit.MediaLibraryKit";
import { fileIo, fileUri, ReadOptions, WriteOptions } from "@kit.CoreFileKit";


export class AppUtils {

    static async savePixelMapToAlbum(context: common.UIAbilityContext, pixelMap: image.PixelMap) {
        try {

            const imagePackerApi = image.createImagePacker();
            const buffer = await imagePackerApi.packToData(pixelMap, { format: 'image/png', quality: 100 })
            const filePath = context?.filesDir + `/${(new Date).getTime()}.png`
            try {
                const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE)
                fileIo.writeSync(file.fd, buffer)
                fileIo.closeSync(file.fd)

                const config: photoAccessHelper.PhotoCreationConfig[] = []
                config.push({
                    fileNameExtension: 'png',
                    photoType: photoAccessHelper.PhotoType.IMAGE,
                })

                const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context)
                const srcUri = [fileUri.getUriFromPath(filePath)]
                const desUris = await phAccessHelper.showAssetsCreationDialog(srcUri, config)
                AppUtils.copyToPhoto(filePath, desUris[0])

            } catch (error) {
                console.error(JSON.stringify(error))
            }

        } catch (error) {
            console.error(`操作失败: ${error.code}, ${error.message}`);
        }

    }

    // 保存到指定路径
    private static copyToPhoto(srcFilePath: string, destFilePath: string) {
        const srcFile = fileIo.openSync(srcFilePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
        const destFile = fileIo.openSync(destFilePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE)
        // 读取源文件内容并写入至目的文件
        const stat = fileIo.statSync(srcFilePath)
        const bufSize = stat.size;
        let readSize = 0;
        const buf = new ArrayBuffer(bufSize);
        const readOptions: ReadOptions = {
            offset: readSize,
            length: bufSize
        };
        let readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
        while (readLen > 0) {
            readSize += readLen;
            let writeOptions: WriteOptions = {
                length: readLen
            };
            fileIo.writeSync(destFile.fd, buf, writeOptions);
            readOptions.offset = readSize;
            readLen = fileIo.readSync(srcFile.fd, buf, readOptions);
        }
        // 关闭文件
        fileIo.closeSync(srcFile);
        fileIo.closeSync(destFile);
    }
}

演示:

cke_20763.png

cke_27066.png

实测了一下仙银的代码,保存成功,

  1. 保存操作
    • onSave方法在用户点击按钮时被调用。
    • 检查offContext是否存在,如果存在,则获取像素数据并打包为PNG格式。
    • 使用文件IO API打开一个文件,将打包后的数据写入文件。
    • 使用photoAccessHelper将文件保存到相册中。

cke_730.jpeg

有个组件截图的功能你可以看下 ,也可以使用三方库

组件截图地址

组件截图

这个很常规的吧

在HarmonyOS Next中,Canvas绘图可以导出为PNG或JPEG文件。通过Canvas组件的getPixelMap()方法获取PixelMap图像数据,再使用imagePacker接口将PixelMap编码为PNG或JPEG格式的ArrayBuffer。保存到相册需使用媒体库管理接口,通过photoAccessHelper.createAsset()将图像数据写入设备相册。

在HarmonyOS Next中,Canvas绘图内容可以导出为PNG或JPEG格式的图片文件,并保存到设备相册。具体实现步骤如下:

  1. Canvas转图片数据:使用Canvas的toDataURL()方法获取Base64编码的图片数据,或通过toBlob()方法获取二进制数据。

  2. 写入应用沙箱:将图片数据写入应用沙箱目录(例如tempcache目录),生成临时图片文件。

  3. 保存到相册:通过PhotoAccessHelper系统能力,将临时图片文件移动到公共相册目录。关键步骤包括:

    • 申请ohos.permission.READ_IMAGEVIDEOohos.permission.WRITE_IMAGEVIDEO权限。
    • 使用PhotoAccessHelper.getPhotoAccessHelper()获取助手实例。
    • 通过createAsset()方法在相册中创建资源,并写入图片数据。
  4. 示例代码片段

// 获取Canvas元素并导出Base64数据
const canvas = this.$refs.canvas;
const dataURL = canvas.toDataURL('image/png');

// 将Base64数据转换为Uint8Array
const buffer = base64ToUint8Array(dataURL.split(',')[1]);

// 使用PhotoAccessHelper保存到相册
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const options = {
  title: '我的画作.png'
};
phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'png', options, (err, uri) => {
  if (!err) {
    // 将图片数据写入uri对应的文件
  }
});

注意:需在module.json5中声明所需权限和PhotoAccessHelper的依赖。此方法适用于HarmonyOS Next的Stage模型。

回到顶部