鸿蒙5.0开辟进阶:图片压缩方案实现案例

打印 上一主题 下一主题

主题 960|帖子 960|积分 2890

往期鸿蒙全套实战文章必看:



  • 鸿蒙开辟焦点知识点,看这篇文章就够了
  • 最新版!鸿蒙HarmonyOS Next应用开辟实战学习路线
  • 鸿蒙HarmonyOS NEXT开辟技术最全学习路线指南
  • 鸿蒙应用开辟实战项目,看这一篇文章就够了(部分项目附源码)

介绍

图片压缩在应用开辟中是一个非经常见的需求,比如在处置惩罚用户上传图片时,必要上传指定大小以内的图片。现在图片压缩支持jpeg、webp、png格式。本例将介绍怎样通过packing和scale实现图片压缩(如自动压缩到目标大小以内,手动调整图片质量和尺寸进行压缩等),以及把图片压缩成差别格式后保存到图库。
效果图预览



使用说明

  • 进入页面,点击添加图片,从拉起的图库中选择一张图片,点击完成。在应用页面压缩前一栏会显示图片的大小以合格式。
  • 压缩模式选择自动模式,输入图片压缩目标大小,可以自定义调整scale每次缩小倍数(相关参数说明点击右侧?资助图标查看)。压缩偏好选择优先压缩质量,可以自定义调整packing最小二分单元(相关参数说明点击右侧?资助图标查看)。可以选择图片压缩的输出格式。点击压缩按钮开始压缩,压缩完成后提示压缩完成,并在压缩后一栏显示压缩后预估的图片大小(压缩后所展示的图片大小,是该图片在内存中作为ArrayBuffer数据的压缩后大小,这一数值并不直接等同于该图片在终极保存到相册时的现实文件大小),以及压缩后的图片格式。点击保存到图库按钮,保存完成后提示已保存到相册。打开图库相册可查看保存的图片,相册中图片格式和压缩后一栏显示的格式划一,图片大小和压缩后一栏显示的图片大小相近。
  • 压缩模式选择自动模式,输入图片压缩目标大小,可以自定义调整scale每次缩小倍数。压缩偏好选择优先压缩尺寸,可以自定义调整最低图片质量(相关参数说明点击右侧?资助图标查看)。可以选择图片压缩的输出格式。点击压缩按钮开始压缩,压缩完成后提示压缩完成,并在压缩后一栏显示压缩后预估的图片大小,以及压缩后的图片格式。点击保存到图库按钮,保存后提示已保存到相册。打开图库相册可查看保存的图片,相册中图片格式和压缩后一栏显示的格式划一,图片大小和压缩后一栏显示的图片大小相近。
  • 压缩模式选择手动模式,可以自定义调整图片质量和图片尺寸(相关参数说明点击右侧?资助图标查看)。可以选择图片压缩的输出格式。点击压缩按钮开始压缩,压缩完成后提示压缩完成,并在压缩后一栏显示压缩后预估的图片大小,以及压缩后的图片格式。点击保存到图库按钮,保存后提示已保存到相册。打开图库相册可查看保存的图片,相册中图片格式和压缩后一栏显示的格式划一,图片大小和压缩后一栏显示的图片大小相近。
  • 点击应用页面上功能项右侧对应的?资助图标可查看对应功能项的相关说明。
  • 手动模式的压缩是指手动调整图片质量和尺寸进行图片压缩。自动模式的压缩是指通过设置图片压缩目标大小,根据设置的相关压缩参数(如scale每次缩小倍数,packing最小二分单元,最低图片质量),将图片自动压缩至最靠近但不超过该压缩目标的大小。但是假如参数设置不合理(如scale每次缩小倍数设置较大,但压缩目标大小又设置很小),大概会出现终极压缩出来的图片达不到设定的压缩目标大小。
  • 自动模式的压缩分为优先压缩图片质量和优先压缩图片尺寸。优先压缩图片质量是指优先通过调整图片质量进行压缩。但是假如图片质量压缩到最低仍旧超过目标大小,则会再接纳scale进行二次压缩。对于通过调整图片质量就能满足目标大小要求的图片,假如想要找到尽大概靠近目标大小的最佳压缩大小,可以调低packing最小二分单元,但相应的压缩性能也会更差一些。优先压缩图片尺寸是指优先通过调整图片尺寸进行压缩。假如对图片质量要求不高但是必要压缩后的图片尺寸尽大概大一些也可以调低最低图片质量。
实现思路


  • 拉起图库选择要压缩的图片。使用photoAccessHelper.PhotoViewPicker创建图库选择器实例photoViewPicker,调用photoViewPicker.select()接口拉起图库界面进行图片选择。图片选择成功后,返回photoSelectResult结果集。从photoSelectResult.photoUris中获取返回图库选择后的媒体文件的uri数组,从而获取图片大小,并在页面上显示选择的图片。
  1. async selectPhotoFromAlbum() {
  2.   // 创建图库选项实例
  3.   const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
  4.   // 设置选择的媒体文件类型为Image
  5.   photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
  6.   // 设置选择媒体文件的最大数目
  7.   photoSelectOptions.maxSelectNumber = 1;
  8.   // 创建图库选择器实例
  9.   const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
  10.   // 调用photoViewPicker.select()接口拉起图库界面进行图片选择,图片选择成功后,返回photoSelectResult结果集。
  11.   photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => {
  12.     // select返回的uri权限是只读权限,需要将uri写入全局变量@State中即可进行读取文件数据操作。
  13.     this.uris = photoSelectResult.photoUris;
  14.     this.photoCount = this.uris.length;
  15.     if (this.photoCount > 0) {
  16.       const ALBUM_PATH: string = photoSelectResult.photoUris[0];
  17.       // 找到最后一个点(.)的索引位置
  18.       let lastDotIndex = ALBUM_PATH.lastIndexOf('.');
  19.       // 使用slice方法从最后一个点之后的位置开始截取字符串到末尾
  20.       this.beforeCompressFmt =
  21.         ALBUM_PATH.slice(lastDotIndex + 1) === 'jpg' ? 'jpeg' : ALBUM_PATH.slice(lastDotIndex + 1);
  22.       this.afterCompressFmt = this.beforeCompressFmt;
  23.       // 读取选择图片的buffer
  24.       const file = fs.openSync(ALBUM_PATH, fs.OpenMode.READ_ONLY);
  25.       // 获取选择图片的字节长度
  26.       this.beforeCompressByteLength = fs.statSync(file.fd).size;
  27.       fs.closeSync(file);
  28.     }
  29.   }).catch((err: BusinessError) => {
  30.     hilog.error(0x0000, TAG, `PhotoViewPicker.select failed :, error code: ${err.code}, message: ${err.message}.`);
  31.   })
  32. }
复制代码

  • 手动模式压缩图片。先获取从图库选择的图片uris,然后将图片数据读取到buffer。通过createImageSource(buffer)创建图片源实例,设置解码参数DecodingOptions,传入createPixelMap创建PixelMap图片对象originalPixelMap。使用scale进行图片尺寸压缩,使用packing进行图片质量压缩。
  1. manualCompression() {
  2.   const ALBUM_PATH: string = this.uris[0];
  3.   const file = fs.openSync(ALBUM_PATH, fs.OpenMode.READ_ONLY);
  4.   let buffer = new ArrayBuffer(fs.statSync(file.fd).size);
  5.   fs.readSync(file.fd, buffer);
  6.   fs.closeSync(file);
  7.   const decodingOptions: image.DecodingOptions = { editable: true };
  8.   const imageSource: image.ImageSource = image.createImageSource(buffer);
  9.   imageSource.createPixelMap(decodingOptions).then(async (originalPixelMap: image.PixelMap) => {
  10.     // 使用scale对图片进行缩放。入参分别为图片宽高的缩放倍数
  11.     await originalPixelMap.scale(this.imageScaleVal / 100, this.imageScaleVal / 100);
  12.     // savePixelMap用于把压缩后的图片保存到图库时使用。由于保存图片时调用的packToFile内部会做类似packing的处理,所以这里只保存scale缩放尺寸后的PixelMap。
  13.     this.savePixelMap = originalPixelMap;
  14.     // packing压缩图片
  15.     let compressedImageData: ArrayBuffer =
  16.       await this.packing(originalPixelMap, this.imageQualityVal, this.afterCompressFmt);
  17.     // 压缩后的ArrayBuffer数据转PixelMap
  18.     let imageSource = image.createImageSource(compressedImageData);
  19.     let opts: image.DecodingOptions = { editable: true };
  20.     // showPixelMap用于显示压缩后的图片
  21.     this.showPixelMap = await imageSource.createPixelMap(opts);
  22.     // showCompressFormat用于显示压缩后的图片格式
  23.     this.showCompressFormat = this.afterCompressFmt;
  24.     // 显示估算packing压缩后的图片大小。该图片在内存中作为ArrayBuffer数据的压缩后大小,这一数值并不直接等同于该图片在最终保存到相册时的实际文件大小。
  25.     this.afterCompressionSize = (compressedImageData.byteLength / BYTE_CONVERSION).toFixed(1);
  26.     promptAction.showToast({ message: $r('app.string.image_compression_compress_completed') });
  27.   }).catch((err: BusinessError) => {
  28.     hilog.error(0x0000, TAG, `Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.`);
  29.   });
  30. }
复制代码

  • 自动模式(指定压缩目标大小)优先压缩图片尺寸。优先使用scale对图片进行尺寸缩放,接纳while循环每次递减reduceScaleVal倍数(对应‘scale每次缩小倍数’)进行尺寸缩放,再用packing(其中图片质量参数quality根据‘最低图片质量’设置)获取压缩后的图片大小,终极查找压缩到最靠近指定图片压缩目标的大小,并获取图片压缩数据用于后续图片保存。
    1. async scalePriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number, quality: number) {
    2.   // ...
    3.   // scale压缩图片尺寸。采用while循环每次递减reduceScaleVal,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。
    4.   let imageScale = 1; // 定义图片宽高的缩放倍数,1表示原比例。
    5.   const REDUCE_SCALE = this.reduceScaleVal;
    6.   const AFTER_COMPRESS_FMT = this.afterCompressFmt;
    7.   // 判断压缩后的图片大小是否大于指定图片的压缩目标大小,如果大于,继续降低缩放倍数压缩。
    8.   while (compressedImageData.byteLength > maxCompressedImageSize * BYTE_CONVERSION) {
    9. if (imageScale > 0) {
    10.    // 性能知识点: 由于scale会直接修改图片PixelMap数据,所以不适用二分查找scale缩放倍数。这里采用循环递减reduceScaleVal缩放图片,
    11.    // 来查找确定最适合的缩放倍数。如果对图片压缩质量要求不高,建议调高每次递减的缩放倍数,减少循环,提升scale压缩性能。
    12.    imageScale = imageScale - REDUCE_SCALE; // 每次缩放倍数
    13.    // 使用scale对图片尺寸进行缩放
    14.    await sourcePixelMap.scale(imageScale, imageScale);
    15.    // packing压缩
    16.    compressedImageData = await this.packing(sourcePixelMap, quality, AFTER_COMPRESS_FMT);
    17. } else {
    18.    // imageScale缩放倍数小于等于0时,没有意义,结束压缩。
    19.    break;
    20. }
    21.   }
    22.   // ...
    23. }
    复制代码
  • 自动模式(指定压缩目标大小)优先压缩图片质量。先判断设置图片质量参数为0时,packing能压缩到的图片最小字节大小compressedImageData.byteLength是否满足指定的图片压缩大小。假如满足,则使用packing方式二分查找最靠近指定图片压缩目标大小的quality来压缩图片。假如不满足,则图片质量按最低0进行设置,并调用scalePriorityCompress进行scale尺寸压缩。
  1. // 优先压缩图片质量
  2. async qualityPriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number) {
  3.   let compressedImageData: ArrayBuffer =
  4.     await this.packing(sourcePixelMap, IMAGE_QUALITY_ZERO, this.afterCompressFmt);
  5.   // 先判断图片质量参数设置最低0能否满足目标大小。如果能满足目标大小,则直接使用packing二分图片质量。如果不满足,则质量参数固定为0,进行scale尺寸压缩
  6.   if (compressedImageData.byteLength <= maxCompressedImageSize * BYTE_CONVERSION) {
  7.     // 满足目标大小,直接使用packing二分
  8.     await this.packingImage(sourcePixelMap, compressedImageData, maxCompressedImageSize * BYTE_CONVERSION);
  9.   } else {
  10.     // 不满足目标大小,质量参数设置为0,再进行scale尺寸压缩
  11.     await this.scalePriorityCompress(sourcePixelMap, maxCompressedImageSize, IMAGE_QUALITY_ZERO);
  12.   }
  13.   // 更新显示压缩后的图片格式
  14.   this.showCompressFormat = this.afterCompressFmt;
  15. }
  16. // packing二分方式循环压缩
  17. async packingImage(sourcePixelMap: image.PixelMap, compressedImageData: ArrayBuffer, maxCompressedImageByte: number) {
  18.   let imageQuality: number = 0;
  19.   const DICHOTOMY_ACCURACY = this.minBisectUnit;
  20.   // 图片质量参数范围为0-100,这里以minBisectUnit为最小二分单位创建用于packing二分图片质量参数的数组。
  21.   const packingArray: number[] = [];
  22.   // 性能知识点: 如果对图片压缩质量要求不高,建议调高minBisectUnit(对应‘packing最小二分单位’),减少循环,提升packing压缩性能。
  23.   for (let i = 0; i <= 100; i += DICHOTOMY_ACCURACY) {
  24.     packingArray.push(i);
  25.   }
  26.   let left = 0; // 定义二分搜索范围的左边界
  27.   let right = packingArray.length - 1; // 定义二分搜索范围的右边界
  28.   const AFTER_COMPRESS_FMT = this.afterCompressFmt;
  29.   // 二分压缩图片
  30.   while (left <= right) {
  31.     const mid = Math.floor((left + right) / 2); // 定义二分搜索范围的中间位置
  32.     imageQuality = packingArray[mid]; // 获取二分中间位置的图片质量值
  33.     // 根据传入的图片质量参数进行packing压缩,返回压缩后的图片文件流数据。
  34.     compressedImageData = await this.packing(sourcePixelMap, imageQuality, AFTER_COMPRESS_FMT);
  35.     // 判断查找一个尽可能接近但不超过压缩目标的压缩大小
  36.     if (compressedImageData.byteLength <= maxCompressedImageByte) {
  37.       // 二分目标值在右半边,继续在更高的图片质量参数(即mid + 1)中搜索
  38.       left = mid + 1;
  39.       // 判断mid是否已经二分到最后,如果二分完了,退出
  40.       if (mid === packingArray.length - 1) {
  41.         break;
  42.       }
  43.       // 获取下一次二分的图片质量参数(mid+1)压缩的图片文件流数据
  44.       compressedImageData = await this.packing(sourcePixelMap, packingArray[mid + 1], AFTER_COMPRESS_FMT);
  45.       // 判断用下一次图片质量参数(mid+1)压缩的图片大小是否大于指定图片的压缩目标大小。如果大于,说明当前图片质量参数(mid)压缩出来的
  46.       // 图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid,得到最终目标图片压缩数据。
  47.       if (compressedImageData.byteLength > maxCompressedImageByte) {
  48.         compressedImageData = await this.packing(sourcePixelMap, packingArray[mid], AFTER_COMPRESS_FMT);
  49.         break;
  50.       }
  51.     } else {
  52.       // 目标值不在当前范围的右半部分,将搜索范围的右边界向左移动,以缩小搜索范围并继续在下一次迭代中查找左半部分。
  53.       right = mid - 1;
  54.     }
  55.   }
  56.   // ...
  57. }
复制代码

  • 压缩后的图片数据保存到相册。通过photoAccessHelper.getPhotoAccessHelper获取相册管理模块的实例,使用createAsset创建图片资源,然后使用createImagePacker创建ImagePacker实例。末了调用imagePacker.packToFile传入压缩后的PixelMap图片源,对应的图片格式和质量参数packOpts,编码后打包进图片文件,图片将自动保存到相册。必要说明packToFile内部会进行packing操纵,所以传入packToFile的PixelMap对象只是scale尺寸缩放后的图片数据,终极必要压缩的图片质量通过packOpts进行设置。
  1. async saveImageToAlbum(): Promise<void> {
  2.   // 获取相册管理模块的实例
  3.   const HELPER = photoAccessHelper.getPhotoAccessHelper(this.context);
  4.   // 指定待创建的文件类型、后缀和创建选项,创建图片资源
  5.   const URI = await HELPER.createAsset(photoAccessHelper.PhotoType.IMAGE, this.afterCompressFmt);
  6.   let file = await fs.open(URI, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  7.   let imagePacker = image.createImagePacker();
  8.   let packOpts: image.PackingOption = {
  9.     format: 'image/' + this.afterCompressFmt,
  10.     quality: this.isAutoMode ? this.autoModeQuality : this.imageQualityVal
  11.   };
  12.   // 指定打包参数,将PixelMap图片源编码后直接打包进文件
  13.   imagePacker.packToFile(this.savePixelMap, file.fd, packOpts, async (err: BusinessError) => {
  14.     if (err) {
  15.       hilog.error(0x0000, TAG, `Failed to pack the image to file, error code: ${err.code}, message: ${err.message}.`);
  16.     } else {
  17.       promptAction.showToast({ message: $r('app.string.image_compression_save_image_msg') });
  18.     }
  19.     // TODO 知识点:使用packToFile方法,需要调用imagePacker.release主动释放imagePacker,打开图库时才能看到新存入的图片
  20.     await fs.close(file.fd).finally(() => {
  21.       imagePacker.release();
  22.     })
  23.   })
  24. }
复制代码
高性能知识点

本示例packing方式压缩图片时,使用二分查找最靠近指定图片压缩目标大小的图片质量quality来压缩图片,提拔查找性能。
工程布局&模块范例

  1. imagecompression                               // har类型
  2. |---view
  3. |   |---ImageCompression.ets                   // 视图层-图片压缩页面
  4. |   |---HelpDescription.ets                    // 视图层-自定义帮助组件
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

来自云龙湖轮廓分明的月亮

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表