HarmonyOS 鸿蒙Next基于PhotoViewPicker对图片进行操作

发布于 1周前 作者 wuwangju 最后一次编辑是 5天前 来自 鸿蒙OS

HarmonyOS 鸿蒙Next基于PhotoViewPicker对图片进行操作

场景描述

  用户有时需要分享或保存图片、视频等用户文件,开发者可以通过系统预置的文件选择器(FilePicker,实现该能力。通过Picker访问相关文件,将拉起对应的应用,引导用户完成界面操作,接口本身无需申请权限。PhotoViewPicker:适用于图片或视频类型文件的选择与保存。优选使用PhotoAccessHelperPhotoViewPicker来选择文件。当前PhotoViewPicker对接的选择资源来自于图库,保存位置为系统文件管理器的特定目录,因此使用save接口保存的图片或视频无法在图库中展示。如需在图库中展示,请使用安全控件创建媒体资源

场景一:从图库获取图片,并通过image组件显示

场景二:对图库获取的图片进行操作

场景三:保存图片

方案描述

场景一:从图库获取图片,并通过image组件显示

效果图

image.png

image.png

方案

  1. 创建图库选择器实例,调用select()接口拉起图库界面进行文件选择。文件选择成功后,返回PhotoSelectResult结果集。
  2. select返回的uri权限是只读权限,可以根据结果集中uri进行读取文件数据操作。
  3. 根据返回uri创建pixelMap
  4. pixelMap通过image组件送显

核心代码

Image(this.pixelMap).width(200).height(200)

  Button(‘打开相册’)

          .onClick(() => {

            //创建图库选择器对象实例

            const photoViewPicker = new picker.PhotoViewPicker();

            //调用select()接口拉起图库界面进行文件选择,文件选择成功后,返回PhotoSelectResult结果集

            photoViewPicker.select().then(async (photoSelectResult: picker.PhotoSelectResult) => {

              //用一个全局变量存储返回的uri

              selectUris = photoSelectResult.photoUris;

              console.info(‘photoViewPicker.select to file succeed and uris are:’ + selectUris);

              //使用fs.openSync接口,通过uri打开这个文件得到fd

              let file = fs.openSync(selectUris[0], fs.OpenMode.READ_ONLY);

              console.info('file fd: ’ + file.fd);

              //根据文件fd创建imagSource

              const imageSource: image.ImageSource = image.createImageSource(file.fd);

              //完成后关闭fd

              fs.closeSync(file);

              let decodingOptions: image.DecodingOptions = {

                editable: true,

                desiredPixelFormat: 3,

              }

              //创建pixelMap

              imageSource.createPixelMap(decodingOptions).then(async (pixelMap1: image.PixelMap) => {

                this.pixelMap = pixelMap1;

              });

            }).catch((err: BusinessError) => {

              console.error(Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message});

            })

          })

场景二:对图库获取的图片进行操作

效果图

image.png

方案

  •  调用pixelMap rotate方法实现对图面的旋转
  •  通过imagePackerapi实现图片编码压缩

核心代码

  Button(“图片操作”)

          .margin({ top: 20 })

          .onClick(async (event: ClickEvent) => {

            if (this.pixelMap) {

              //旋转90度

              await this.pixelMap.rotate(90);

              //创建图像编码ImagePacker对象。

              const imagePackerApi = image.createImagePacker();

              //设置编码输出流和编码参数。

              // format为图像的编码格式;quality为图像质量,范围从0-100,100为最佳质量。

              let packOpts: image.PackingOption = { format: “image/jpeg”, quality: 100 };

              //通过PixelMap进行编码

              imagePackerApi.packing(this.pixelMap, packOpts).then(async (data: ArrayBuffer) => {

                // data 为打包获取到的文件流,写入文件保存即可得到一张图片

                arrayBuffer = data;

              }).catch((error: BusinessError) => {

                console.error('Failed to pack the image. And the error is: ’ + error);

              })

            }

          })

场景三:保存图片

方案一:通过photoViewPicker将图片保存到系统文件管理器管理特定目录

         当前所有pickersave接口都是用户可感知的,具体行为是拉起FilePicker, 将文件保存在系统文件管理器管理的特定目录,与图库管理的资源隔离,无法在图库中看到。

效果图

image.png

    1. 调用save()接口拉起FilePicker界面进行文件保存。用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作。保存成功后,并用一个全局变量存储返回的uri
    2. 使用fs.openSync接口,通过选择和保存uri打开这两个文件得到fd,这里需要注意接口权限参数分别是fs.OpenMode.READ_ONLYfs.OpenMode.WRITE_ONLY。再调用fs.copyFileSync接口进行复制,修改完成后关闭两个文件。

核心代码

try {

              const photoSaveOptions = new picker.PhotoSaveOptions(); // 创建文件管理器保存选项实例

              photoSaveOptions.newFileNames = [“PhotoViewPicker01.png”]; // 保存文件名(可选),方括号里的文件名自定义,每次不能重复,设备里已有这个文件的话,名字就需要改个不一样的,不然接口会报错

              const photoViewPicker = new picker.PhotoViewPicker();

              try {

                //调用save()接口拉起FilePicker界面进行文件保存。用户选择目标文件夹,用户选择与文件类型相对应的文件夹,即可完成文件保存操作

                let photoSaveResult = await photoViewPicker.save(photoSaveOptions);

                if (photoSaveResult != undefined) {

                  //保存成功后,并用一个全局变量存储返回的uri

                  saveUris = photoSaveResult;

                  console.info(‘photoViewPicker.save to file succeed and uris are:’ + photoSaveResult);

                }

              } catch (error) {

                let err: BusinessError = error as BusinessError;

                console.error([picker] Invoke photoViewPicker.save failed, code is ${err.code}, message is ${err.message});

              }

            } catch (error) {

              let err: BusinessError = error as BusinessError;

              console.info("[picker] photoViewPickerSave error = " + JSON.stringify(err));

            }

 try {

              //使用fs.openSync接口,通过选择和保存uri打开这两个文件得到fd,这里需要注意接口权限参数分别是fs.OpenMode.READ_ONLY和fs.OpenMode.WRITE_ONLY

              let photoSelect = fs.openSync(selectUris[0], fs.OpenMode.READ_ONLY);

              let photoSave = fs.openSync(saveUris[0], fs.OpenMode.WRITE_ONLY);

              //再调用fs.copyFileSync接口进行复制

              fs.copyFileSync(photoSelect.fd, photoSave.fd);

              //完成后关闭两个文件。

              fs.close(photoSelect);

              fs.close(photoSave);

            } catch (error) {

              let err: BusinessError = error as BusinessError;

              console.info("[picker] Photo Save error = " + JSON.stringify(err));

            }

          })

方案二:通过安全控件按钮保存图片到图库

保存控件是一种特殊的安全控件,它允许用户通过点击按钮临时获取存储权限,而无需通过权限弹框进行授权确认

集成保存控件后,当用户点击该控件时,应用会获得10秒内单次访问媒体库特权接口的授权。这适用于任何需要将文件保存到媒体库的应用场景,例如保存图片或视频等。

与需要触发系统应用并由用户选择具体保存路径的Picker不同,保存控件可以直接保存到媒体库路径,使得操作更为便捷。

使用场景:应用仅需要在前台期间,短暂使用保存图片的特性,不需要长时间使用。此时,可以直接使用安全控件中的保存控件,免去权限申请和权限请求等环节(创建媒体资源需要在应用中申请相册管理模块权限’ohos.permission.WRITE_IMAGEVIDEO’),获得临时授权,保存对应图片。

约束与限制

      • 应用在onClick()触发回调到调用媒体库特权接口的时间间隔不能大于10秒。
      • 用户点击一次控件,仅获取一次授权调用。
      • 为了保障用户的隐私不被恶意应用获取,应用需确保安全控件是可见的且用户能够识别的。开发者需要合理的配置控件的尺寸、颜色等属性,避免视觉混淆的情况,如果发生因控件的样式不合法导致授权失败的情况,请检查设备错误日志。

效果图

image.png

        1. 设置安全控件按钮属性。
        2. 创建安全控件按钮。
        3. 调用PhotoAccessHelper.createAsset接口创建图片资源。
        4. 根据资源uri创建file并写入图片数据。

核心代码

 @State saveButtonOptions: SaveButtonOptions = {

    icon: SaveIconStyle.FULL_FILLED, //设置保存按钮的图标风格,不传入该参数表示没有图标,icon和text至少存在一个;这里设置为:保存按钮展示填充样式图标。

    text: SaveDescription.SAVE_IMAGE, //设置保存按钮的文本描述, 不传入该参数表示没有文字描述,这里设置为:保存按钮的文字描述为“保存图片”。

    buttonType: ButtonType.Capsule   //设置保存按钮的背景样式,   不传入该参数表示没有背景。这里设置为:胶囊型按钮(圆角默认为高度的一半)。

  } // 设置安全控件按钮属性

SaveButton(this.saveButtonOptions)// 创建安全控件按钮

  .padding(10)

    //点击动作触发该回调。

  .onClick(async (event, result: SaveButtonOnClickResult) => {

    //SaveButtonOnClickResult枚举说明

    //名称 枚举值  描述

    // SUCCESS 0  保存按钮点击成功。

    // TEMPORARY_AUTHORIZATION_FAILED  1  保存按钮点击后权限授权失败。

    if (result == SaveButtonOnClickResult.SUCCESS) {

      // result:存储权限的授权结果,授权时长为10秒,即触发点击后,可以在10秒之内不限制次数的调用特定媒体库接口,超出10秒的调用会鉴权失败。

      try {

        let context = getContext();

        //获取相册管理模块的实例,用于访问和修改相册中的媒体文件

        let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);

        // onClick触发后10秒内通过createAsset接口创建图片文件,10秒后createAsset权限收回

        let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, ‘jpg’); // 创建媒体文件

        console.info('createAsset successfully, uri: ’ + uri);

        let file = await fs.open(uri, fs.OpenMode.READ_WRITE || fs.OpenMode.CREATE);

        //   // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制

        await fs.write(file.fd, arrayBuffer);

        /// 关闭文件

        await fs.close(file);

        promptAction.showToast({ message: ‘已保存至相册!’ });

      } catch (err) {

        console.error('createAsset failed, message = ', err);

      }

    } else {

      console.error(‘SaveButtonOnClickResult createAsset failed’);

    }

  })

方案三:动态申请acl权限,保存图片到图库

        PhotoAccessHelper.createAsset接口 需要ohos.permission.WRITE_IMAGEVIDEO权限ohos.permission.WRITE_IMAGEVIDEO权限为系统等级,

       该权限当前可申请的场景与功能:应用需要克隆、备份或同步图片/视频类文件。  

权限等级应用APL等级是一一对应的。原则上,拥有低APL等级的应用默认无法申请更高等级的权限。访问控制列表ACLAccess Control List)提供了解决低等级应用访问高等级权限问题的特殊渠道。

image.png

允许ACL跨级别申请

当前仅支持部分权限通过应用市场(AGC)使用ACL的方式跨级别申请权限。在申请发布Profile时,同步提交申请ACL权限

系统权限均定义了“ACL使能”字段,如果应用需要使用跨级别权限时,需使用ACL方式来申请对应权限。在应用权限列表中标记“ACL使能:TRUE”的为支持ACL的权限。当该权限的ACL使能为TRUE,应用可以使用ACL方式跨级别申请该权限。

支持ACL权限

DevEco Studio 4.0 Release版本起,针对HarmonyOS工程,DevEco Studio支持在调测阶段通过自动签名快速申请ACL权限。

效果图

image.png

  • module.json5文件中配置权限
  • requestPermissionsFromUser方法弹框向用户动态申请权限
  • 权限申请成功后通过createAsset接口创建图片文件

核心代码

“requestPermissions”: [

{

  “name”: “ohos.permission.WRITE_IMAGEVIDEO”,

“reason”: “$string:app_name”,

“usedScene”: {

  “abilities”: [

  “FormAbility”

  ],

  “when”:“always”

}

}

]

async requestPermissionsFn() {

  Logger.info(TAG, requestPermissionsFn entry);

  try {

    //申请相册管理模块权限’ohos.permission.WRITE_IMAGEVIDEO’

    this.atManager.requestPermissionsFromUser(this.appContext, [

      ‘ohos.permission.WRITE_IMAGEVIDEO’

    ]).then(() => {

      Logger.info(TAG, request Permissions success!);

      //权限申请成功,保存到图库

      this.SavePicture()

    })

  } catch (err) {

    Logger.info(TAG, requestPermissionsFromUser call Failed! error: ${err.code});

  }

}

async SavePicture(): Promise<void> {

  //

  try {

  let context = getContext();

  //获取相册管理模块的实例,用于访问和修改相册中的媒体文件

  let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);

  //通过createAsset接口创建图片文件

  let uri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, ‘jpg’); // 创建媒体文件

  console.info('createAsset successfully, uri: ’ + uri);

  let file = await fs.open(uri, fs.OpenMode.READ_WRITE || fs.OpenMode.CREATE);

  // 使用uri打开文件,可以持续写入内容,写入过程不受时间限制

  await fs.write(file.fd, arrayBuffer);

  // 关闭文件

  await fs.close(file);

} catch (err) {

  console.error('createAsset failed, message = ', err);

}

5 回复
就不能吧写好的 demo 放出路径吗 或者放在 Gitee上面吗

您好,当前技术文章配套的demo工程正在外发中,后续会发布至gitee上,敬请关注!

从相册选择一张图片上传服务端,但是图片太大,要先压缩再上传, 有压缩图片的方法吗
请问一下,该demo是否已经发布到gitee了呢

在HarmonyOS(鸿蒙)开发中,若你正在使用PhotoViewPicker进行图片选择并希望进行进一步操作(如预览、编辑、上传等),首先确保你的项目已正确集成PhotoViewPicker组件并获得了必要的权限(如读写存储权限)。

操作图片时,通常会在选择图片后通过回调接口获取图片路径或Bitmap对象。之后,可以使用鸿蒙提供的图形API(如Canvas, Paint等)进行绘制或修改图片,或使用第三方库进行更复杂的处理。

请检查你的回调逻辑是否正确处理了图片数据,并确保在UI线程中更新UI。

鸿蒙Next学习地址:https://www.itying.com/category-93-b0.html

回到顶部