IT评测·应用市场-qidao123.com

标题: 鸿蒙5.0开辟进阶:图片压缩方案实现案例 [打印本页]

作者: 来自云龙湖轮廓分明的月亮    时间: 2024-12-31 15:10
标题: 鸿蒙5.0开辟进阶:图片压缩方案实现案例
往期鸿蒙全套实战文章必看:



介绍

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



使用说明
实现思路

  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. }
复制代码
  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. }
复制代码
  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. }
复制代码
  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企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4