应用内组件截图并保存到用户文件

应用内组件截图并保存到用户文件

HarmonyOS
2024-06-11 23:05:15
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
truemichael

在浏览器的工具栏中,截图是常见的功能之一。本文介绍如何使用OH API10新增的componentSnapshot接口实现应用内组件截图,然后将截屏接口输出的图片通过FilePicker保存到系统文管,或者通过photoAccessHelper 保存到系统图库。

效果呈现

运行环境

SDK API 10

实现思路

场景一:截图通过FilePicker保存到系统文管

组件截图使用componentSnapshot接口即可,但是参考文档文件管理-保存用户文件过程中,发现在开发者视角下将图片保存到系统文管存在2个问题:

1. photoViewPicker.save 拉起FilePicker系统文管界面,然后用户选择目标文件夹进行文件保存,保存成功后,返回保存图片文件的uri。如下文档示例:

let uri = null; 
const photoViewPicker = new picker.PhotoViewPicker(); 
photoViewPicker.save(photoSaveOptions).then((photoSaveResult) => { 
  uri = photoSaveResult[0]; 
})

根据photoViewPicker.save接口规范,返回的uri权限是读写权限,

不能在picker的回调里直接使用此uri进行打开文件操作,需要定义一个全局变量保存uri,使用类似一个按钮去触发打开文件。

但是,应用只有一个截屏按钮,在该按钮的onClick回调中需要执行截屏+拉起FilePicker界面选择目标文件夹+读写图片文件操作。

根据

JS的事件循环,如果在picker回调外使用setTimeout延迟执行获取uri后的文件读写操作,开发者无法确定用户在photoViewPicker.save拉起的文件管理器页面停留多长时间,什么时候picker回调执行结束,延迟执行时间未知。如下代码:

photoViewPicker.save(photoSaveOptions).then((photoSaveResult) => { 
  uri = photoSaveResult[0]; 
}) 
// 延迟读写操作的执行 
setTimeout(() => { 
  // 读写逻辑 
}, 1000)

如果用户文件管理器页面操作大于setTimeout的1s,即在picker回调返回之前触发了setTimeout内的读写操作,会由于uri为空而导致文件打开(fs.openSync)时出现

js crash错误。

2.文件写操作接口为writeSync(fd: number, buffer: ArrayBuffer | string),截屏接口输出为PixelMap图片对象,开发者的做法可能会根据接口入参的类型,将PixelMap直接转为ArrayBuffer,然后将ArrayBuffer直接进行读写:

this.pixmap.readPixelsToBuffer(readBuffer).then(() => 
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE); 
fs.writeSvnc(file.fd, readBuffer); 
fs.closeSync(file); 
}

但是这种做法虽然可以成功保存图片,但是会导致图片格式错误,需要一个完整的示例去指导用户进行读写图片操作。

针对问题1,使用@Watch监听photoViewPicker.save返回的uri,当uri更新之后再进行图片文件读写操作,这样可以避免uri为空的js crash错误。

针对问题2,需要先将PixelMap图片对象编码成不同格式的存档图片,然后再用于后续处理,如保存、传输等。

场景二:通过photoAccessHelper 保存到系统图库

由于创建相册为system接口,三方应用不可调用,所本场景只使用public接口保存在图库默认的相册里。更多接口介绍可查看相册管理相关文档。

方案介绍

场景一方案

1. componentSnapshot.get("root"),应用内组件截图,输出PixelMap图片对象

2. photoViewPicker.save(photoSaveOptions),拉起FilePicker选择目标文件夹,保存图片文件,输出文件保存的uri

3. @Watch监听photoViewPicker.save返回的uri,当uri更新之后再进行图片文件读写操作

import componentSnapshot from '@ohos.arkui.componentSnapshot' 
import image from '@ohos.multimedia.image' 
import picker from '@ohos.file.picker' 
import fs from '@ohos.file.fs'; 
 
@Component 
struct Index { 
  @State pixmap: image.PixelMap = undefined; 
  @State @Watch("onPhotoSaveUriUpdate") photoSaveUri: string = undefined; 
 
  // 监听uri的更新,进行用户文件读写操作 
  onPhotoSaveUriUpdate() { 
    if (this.photoSaveUri) { 
      // 创建图像编码ImagePacker对象 
      const imagePacker = image.createImagePacker(); 
      // 设置编码输出流和编码参数 
      let packOpts = { format: "image/png", quality: 100 }; 
      // 进行图片编码 
      imagePacker.packing(this.pixmap, packOpts) 
        .then(data => { 
          // 打开文件 
          let file = fs.openSync(this.photoSaveUri, fs.OpenMode.WRITE_ONLY); 
          // 编码成功,写操作 
          fs.writeSync(file.fd, data); 
          fs.closeSync(file); 
        }) 
        .catch(error => { 
          console.error(TAG + 'Failed to pack the image. And the error is: ' + error); 
        }) 
    } 
  } 
 
  build() { 
    Column() { 
      ... 
      Button("截图") 
        .onClick(() => { 
          // 应用内组件截图,输出PixelMap图片对象 
          componentSnapshot.get("root") 
            .then((pixmap: image.PixelMap) => { 
              this.pixmap = pixmap; 
 
              // FilePicker拉起系统文件管理器,用户选择文件保存路径,返回uri 
              const photoSaveOptions = new picker.PhotoSaveOptions(); // 创建文件管理器 
              photoSaveOptions.newFileNames = [new Date().getTime() + "_screenshot.png"]; 
              const photoViewPicker = new picker.PhotoViewPicker(); 
              photoViewPicker.save(photoSaveOptions) 
                .then((photoSaveResult) => { 
                  this.photoSaveUri = photoSaveResult[0]; 
                }) 
                .catch((err) => { 
                  ... 
                }) 
            }) 
            .catch(err => { 
              ... 
            }) 
        }) 
    } 
    ... 
    .id("root") 
  } 
}

场景二方案

类似于场景一:

1.componentSnapshot.get(“root”),应用内组件截图,输出PixelMap图片对象。

2.phAccessHelper.createAsset,调用createAsset接口创建图片资源,拿到返回的uri。

3。@Watch监听photoViewPicker.save返回的uri,当uri更新之后再进行图片文件读写操作。

import componentSnapshot from '@ohos.arkui.componentSnapshot' 
import image from '@ohos.multimedia.image' 
import fs from '@ohos.file.fs'; 
import photoAccessHelper from '@ohos.file.photoAccessHelper'; 
import promptAction from '@ohos.promptAction'; 
 
@Entry 
@Component 
struct Index { 
  @State pixmap: image.PixelMap = undefined; 
  @State @Watch("onPhotoSaveUriUpdate") photoSaveUri: string = undefined; 
 
  // 监听uri的更新,进行用户文件读写操作 
  onPhotoSaveUriUpdate() { 
    if (this.photoSaveUri) { 
      // 创建图像编码ImagePacker对象 
      const imagePacker = image.createImagePacker(); 
      // 设置编码输出流和编码参数 
      let packOpts = { format: "image/jpeg", quality: 100 }; 
      // 进行图片编码 
      imagePacker.packing(this.pixmap, packOpts) 
        .then(data => { 
          // 打开文件 
          let file = fs.openSync(this.photoSaveUri, fs.OpenMode.WRITE_ONLY); 
          // 编码成功,写操作 
          fs.writeSync(file.fd, data); 
          fs.closeSync(file); 
          promptAction.showToast({ 
            message: '已成功保存至相册', 
            duration: 1000 
          }) 
        }) 
        .catch(error => { 
          console.error(TAG + 'Failed to pack the image. And the error is: ' + error); 
        }) 
    } 
  } 
 
  build() { 
    Column() { 
      Button("截图") 
        .onClick(() => { 
          // 应用内组件截图,输出PixelMap图片对象 
          console.info(TAG + 'start'); 
          componentSnapshot.get("root") 
            .then((pixmap: image.PixelMap) => { 
              this.pixmap = pixmap; 
              let photoType = photoAccessHelper.PhotoType.IMAGE; 
              let extension = 'jpg'; 
              let options = { 
                title: 'testPhoto' 
              } 
              const context = getContext(this); 
              let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); 
 
              phAccessHelper.createAsset(photoType, extension, options, (err, uri) => { 
                if (uri != undefined) { 
                  console.info(TAG + 'createAsset uri' + uri); 
                  this.photoSaveUri = uri 
                  console.info(TAG + 'createAsset successfully'); 
                } else { 
                  console.error(TAG + 'createAsset failed, message = ', err); 
                } 
              }); 
            }) 
 
        }) 
        .width(100) 
        .height(200) 
    } 
    .width('100%') 
    .height('100%') 
    .backgroundColor(Color.White) 
    .id("root") 
  } 
}
分享
微博
QQ
微信
回复
2024-06-12 23:04:31
相关问题
HarmonyOS 下载文件保存到指定目录
38浏览 • 1回复 待解决
获取网络图片保存到相册
1486浏览 • 1回复 待解决
如何将Pixmap保存到本地文件
482浏览 • 1回复 待解决
如何将像素点保存到图片文件
2315浏览 • 1回复 待解决
应用沙箱下的图片保存到图库
1147浏览 • 1回复 待解决
HarmonyOS PhotoViewPicker 保存到图库
624浏览 • 1回复 待解决
怎样保持文件允许用户拷出来?
1906浏览 • 1回复 待解决
Hi3861数据保存到flash
9321浏览 • 1回复 已解决
HarmonyOS如何将PixelMap保存到相册?
570浏览 • 1回复 待解决