如何实现图片压缩
========

#### 问题现象

在应用开发中，图片压缩是一个极为常见的需求，例如在处理用户上传头像、发布图文动态时，往往需要将图片控制在一定大小以内。本文将围绕这一场景，介绍实现图片压缩的四种主流方案。

#### 背景知识

图片压缩的核心目的，是在图片质量、文件体积和解码加载速度之间找到一个最优平衡点。在应用开发中，过大图片不仅消耗用户存储空间、增加网络传输负担，还会在解码时占用大量内存和CPU资源，导致应用卡顿。本文提供了以下四种压缩方案：

| 
压缩方案

 | 

核心原理

 | 

实现手段

 |
| :-- | :-- | :-- |
| 

质量压缩

 | 

改变图像的编码方式，通过舍弃部分不被人眼敏感的细节数据来减小文件体积。它不改变图片的尺寸，但会降低图像的清晰度。

 | 

使用ImagePacker的[packToData](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-imagepacker#packtodata13)方法，通过调整 quality 参数（0-100）来控制压缩程度 。

 |
| 

尺寸压缩

 | 

直接减少图片的像素数量，即缩小图片的宽高。这是一种最直接有效的减小图片内存占用和文件大小的方法。

 | 

操作PixelMap对象的[scale](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-image-pixelmap#scale9)方法，通过传入宽高的缩放倍数来实现 。

 |
| 

智能压缩

 | 

一种目标导向的压缩策略。开发者设定一个期望的“目标文件大小”，系统自动迭代尝试质量压缩和尺寸压缩，直至图片大小最接近且不超过该目标值。

 | 

结合循环与二分法思想，通过不断调整scale倍数和quality参数，反复对PixelMap进行压缩并检查体积，最终找到最优平衡点 。

 |
| 

格式转换

 | 

将图片从一种编码格式转换为另一种，利用不同格式的算法特性来达到压缩目的。例如将有损的JPEG转换为压缩率更高的WebP。

 | 

在ImagePacker的PackingOption中指定输出format（如'image/webp'），将图片重新编码为指定格式 。

 |

#### 解决方案

图片压缩主要是通过packToData和scale实现图片压缩，通过对图片进行缩放或者设置编码参数进行压缩。具体实现可以参考以下几种方案。

#### \[h2\]方案一：质量压缩

通过修改编码质量参数quality，该参数仅对JPEG图片生效。WebP、PNG等图片均为无损编码。示例代码如下：

_/\*\*_
 _\* 质量压缩_
 _\*/_
async qualityCompressionMethod(quality: number) {
  const qualityCompressionMethod = await this.compressionMethod.compressionMethod(100, quality);
  this.showPixelMap = qualityCompressionMethod\[0\];
  this.savePixelMap = qualityCompressionMethod\[1\];
  this.afterCompressionSize = qualityCompressionMethod\[2\];
  this.showCompressFormat = qualityCompressionMethod\[3\];
}

compressionMethod方法代码如下：

_/\*\*_
 _\* 压缩_
 _\*/_
async compressionMethod(imageScaleVal: number,
  imageQualityVal: number): Promise<\[PixelMap, PixelMap, string, string\]> {
  const ALBUM\_PATH: string = this.uris\[0\];
  const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
  let buffer = new ArrayBuffer(fs.statSync(file.fd).size);
  fs.readSync(file.fd, buffer);
  fs.closeSync(file);
  const decodingOptions: image.DecodingOptions = { editable: true };
  const imageSource: image.ImageSource = image.createImageSource(buffer);
  return new Promise<\[PixelMap, PixelMap, string, string\]>((resolve) => {
    imageSource.createPixelMap(decodingOptions).then(async (originalPixelMap: image.PixelMap) => {
      if (AppStorage.get('sizeFlag') != undefined) {
        this.sizeFlag = AppStorage.get('sizeFlag')!;
      }
      if (this.sizeFlag) {
        _// 使用scale对图片进行缩放。入参分别为图片宽高的缩放倍数_
        await originalPixelMap.scale(imageScaleVal / 100, imageScaleVal / 100);
        _// savePixelMap用于把压缩后的图片保存到图库时使用。由于保存图片时调用的packToFile内部会做类似packing的处理，所以这里只保存scale缩放尺寸后的PixelMap。_
        this.savePixelMap = originalPixelMap;
        this.imageQuality = imageScaleVal;
      }
      _// savePixelMap用于把压缩后的图片保存到图库时使用。_
      this.savePixelMap = originalPixelMap;
      if (AppStorage.get('outputCompressFmt') != undefined) {
        this.afterCompressFmt = AppStorage.get('outputCompressFmt')!;
      }

      _// packing压缩图片_
      let compressedImageData: ArrayBuffer =
        await this.packing(originalPixelMap, imageQualityVal, this.afterCompressFmt);
      _// 压缩后的ArrayBuffer数据转PixelMap_
      let imageSource = image.createImageSource(compressedImageData);
      let opts: image.DecodingOptions = { editable: true };
      _// showPixelMap用于显示压缩后的图片_
      const pixelMap: image.PixelMap = await imageSource.createPixelMap(opts);
      this.showPixelMap = pixelMap;
      _// showCompressFormat用于显示压缩后的图片格式_
      this.showCompressFormat = this.afterCompressFmt;
      _// 显示估算packing压缩后的图片大小。该图片在内存中作为ArrayBuffer数据的压缩后大小，这一数值并不直接等同于该图片在最终保存到相册时的实际文件大小。_
      this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
      resolve(\[this.showPixelMap, this.savePixelMap, this.afterCompressionSize, this.afterCompressFmt\]);
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, \`Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.\`);
    });

  });
}

#### \[h2\]方案二：尺寸压缩

调用scale对图片的宽高进行缩放，缩放后的PixelMap通过packToData进行压缩。尺寸压缩不需要修改编码参数里的图片质量。示例代码如下：

_/\*\*_
 _\* 尺寸压缩_
 _\*/_
async sizeCompressionMethod(size: number) {
  this.sizeFlag = true;
  AppStorage.setOrCreate('sizeFlag', this.sizeFlag);
  const sizeCompressionMethod = await this.compressionMethod.compressionMethod(size, 100);
  this.showPixelMap = sizeCompressionMethod\[0\];
  this.afterCompressionSize = sizeCompressionMethod\[2\];
  this.showCompressFormat = sizeCompressionMethod\[3\];
}

#### \[h2\]方案三：智能压缩

智能压缩通过设置优先压缩图片质量和优先压缩图片尺寸。优先压缩图片质量是指优先通过调整图片质量进行压缩。但是如果图片质量压缩到最低仍然超过目标大小，则会再采用scale进行二次压缩。对于通过调整图片质量就能满足目标大小要求的图片，如果想要找到尽可能接近目标大小的最佳压缩大小，可以调低packing最小二分单位，但相应的压缩性能也会更差一些。优先压缩图片尺寸是指优先通过调整图片尺寸进行压缩。如果对图片质量要求不高但是需要压缩后的图片尺寸尽可能大一些也可以调低最低图片质量。示例代码如下：

_/\*\*_
 _\* 智能压缩_
 _\* @param qualityCompression：是否质量优先_
 _\* @param maxCompressedImageSize 指定图片压缩目标大小，单位kb_
 _\* @param minBisectUnit packing最小二分单位_
 _\* @param minQuality 最低图片质量_
 _\*/_
intelligentCompressionMethod(qualityCompression: boolean, maxCompressedImageSize: number, minBisectUnit: number,
  minQuality: number): Promise<\[PixelMap, PixelMap, string, string\]> {
  _// 获取图库中选择的图片路径_
  const ALBUM\_PATH: string = this.uris\[0\];
  const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
  let buffer: ArrayBuffer = new ArrayBuffer(fs.statSync(file.fd).size);
  _// 读取图片到buffer_
  fs.readSync(file.fd, buffer);
  fs.closeSync(file);
  _// 通过缓冲区创建图片源实例_
  const imageSource: image.ImageSource = image.createImageSource(buffer);
  _// 设置解码参数DecodingOptions，解码获取PixelMap图片对象。_
  const decodingOptions: image.DecodingOptions = {
    editable: true, _// 是否可编辑。当取值为false时，图片不可二次编辑，如crop等操作将失败。_
  };
  return new Promise<\[PixelMap, PixelMap, string, string\]>((resolve, reject) => {
    imageSource.createPixelMap(decodingOptions).then(async (originalPixelMap: image.PixelMap) => {
      try {
        if (AppStorage.get('outputCompressFmt') != undefined) {
          this.afterCompressFmt = AppStorage.get('outputCompressFmt')!;
        }
        _// 压缩图片_
        await this.compressedImage(qualityCompression, originalPixelMap, maxCompressedImageSize, minBisectUnit,
          minQuality);
        this.promptAction.showToast({ message: '智能压缩完成！' });
        resolve(\[this.showPixelMap!, this.savePixelMap!, this.afterCompressionSize, this.showCompressFormat\]);
      } catch (err) {
        hilog.error(0x0000, TAG, \`Failed to compress image, error code: ${err.code}, message: ${err.message}.\`);
        reject(err);
      }
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, \`Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.\`);
      reject(err);
    });
  });
}

优先压缩图片质量示例代码如下：

_/\*\*_
 _\* 优先压缩图片尺寸_
 _\* @param sourcePixelMap 原始待压缩图片的PixelMap_
 _\* @param maxCompressedImageSize 指定图片的压缩目标大小_
 _\* @param quality_
 _\*/_
async scalePriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number, quality: number) {
  let compressedImageData: ArrayBuffer = await this.packing(sourcePixelMap, quality, this.afterCompressFmt);
  _// 如果调整过最低图片质量，先计算最低图片质量是否满足要求。如果设定的最低图片质量已经满足目标大小，则直接返回_
  if (quality !== IMAGE\_QUALITY\_HUNDRED &&
    (compressedImageData.byteLength <= maxCompressedImageSize \* BYTE\_CONVERSION)) {
    _// 保存最终压缩的图片质量，用于保存到图库时使用_
    this.intelligentCompressionQuality = quality;
    _// ArrayBuffer转PixelMap，并保存图片相关数据_
    let imageSource = image.createImageSource(compressedImageData);
    let opts: image.DecodingOptions = { editable: true };
    this.showPixelMap = await imageSource.createPixelMap(opts);
    this.savePixelMap = sourcePixelMap;
    this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
    _// 更新显示压缩后的图片格式_
    this.showCompressFormat = this.afterCompressFmt;
    return;
  }
  _// scale压缩图片尺寸。采用while循环每次递减0.4，最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。_
  let imageScale = 1; _// 定义图片宽高的缩放倍数，1表示原比例。_
  const REDUCE\_SCALE = 0.5;
  const AFTER\_COMPRESS\_FMT = this.afterCompressFmt;
  _// 判断压缩后的图片大小是否大于指定图片的压缩目标大小，如果大于，继续降低缩放倍数压缩。_
  while (compressedImageData.byteLength > maxCompressedImageSize \* BYTE\_CONVERSION) {
    if (imageScale > 0) {
      _// 性能知识点: 由于scale会直接修改图片PixelMap数据，所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片，_
      _// 来查找确定最适合的缩放倍数。如果对图片压缩质量要求不高，建议调高每次递减的缩放倍数，减少循环，提升scale压缩性能。_
      imageScale = imageScale - REDUCE\_SCALE; _// 每次缩放倍数减_
      _// 使用scale对图片尺寸进行缩放_
      await sourcePixelMap.scale(imageScale, imageScale);
      _// packing压缩_
      compressedImageData = await this.packing(sourcePixelMap, quality, AFTER\_COMPRESS\_FMT);
    } else {
      _// imageScale缩放倍数小于等于0时，没有意义，结束压缩。_
      break;
    }
  }
  this.intelligentCompressionQuality = quality;
  let imageSource = image.createImageSource(compressedImageData);
  let opts: image.DecodingOptions = { editable: true };
  this.showPixelMap = await imageSource.createPixelMap(opts);
  this.savePixelMap = sourcePixelMap;
  this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
  this.showCompressFormat = this.afterCompressFmt;
  _// 可能会存在scale每次缩小倍数设置过大，导致无法压缩到图片压缩目标大小，弹窗提示_
  if (compressedImageData.byteLength > maxCompressedImageSize \* BYTE\_CONVERSION) {
    let uiContext = AppStorage.get('context') as UIContext;
    uiContext.showAlertDialog({
      message: '当前在设置的scale每次缩小倍数条件下，无法压缩到目标大小，建议调低scale每次缩小倍数后再试',
      alignment: DialogAlignment.Center
    });
  }
}

优先压缩图片尺寸示例代码如下：

_/\*\*_
 _\* 优先压缩图片尺寸_
 _\* @param sourcePixelMap 原始待压缩图片的PixelMap_
 _\* @param maxCompressedImageSize 指定图片的压缩目标大小_
 _\* @param quality_
 _\*/_
async scalePriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number, quality: number) {
  let compressedImageData: ArrayBuffer = await this.packing(sourcePixelMap, quality, this.afterCompressFmt);
  _// 如果调整过最低图片质量，先计算最低图片质量是否满足要求。如果设定的最低图片质量已经满足目标大小，则直接返回_
  if (quality !== IMAGE\_QUALITY\_HUNDRED &&
    (compressedImageData.byteLength <= maxCompressedImageSize \* BYTE\_CONVERSION)) {
    _// 保存最终压缩的图片质量，用于保存到图库时使用_
    this.intelligentCompressionQuality = quality;
    _// ArrayBuffer转PixelMap，并保存图片相关数据_
    let imageSource = image.createImageSource(compressedImageData);
    let opts: image.DecodingOptions = { editable: true };
    this.showPixelMap = await imageSource.createPixelMap(opts);
    this.savePixelMap = sourcePixelMap;
    this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
    _// 更新显示压缩后的图片格式_
    this.showCompressFormat = this.afterCompressFmt;
    return;
  }
  _// scale压缩图片尺寸。采用while循环每次递减0.4，最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。_
  let imageScale = 1; _// 定义图片宽高的缩放倍数，1表示原比例。_
  const REDUCE\_SCALE = 0.5;
  const AFTER\_COMPRESS\_FMT = this.afterCompressFmt;
  _// 判断压缩后的图片大小是否大于指定图片的压缩目标大小，如果大于，继续降低缩放倍数压缩。_
  while (compressedImageData.byteLength > maxCompressedImageSize \* BYTE\_CONVERSION) {
    if (imageScale > 0) {
      _// 性能知识点: 由于scale会直接修改图片PixelMap数据，所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片，_
      _// 来查找确定最适合的缩放倍数。如果对图片压缩质量要求不高，建议调高每次递减的缩放倍数，减少循环，提升scale压缩性能。_
      imageScale = imageScale - REDUCE\_SCALE; _// 每次缩放倍数减_
      _// 使用scale对图片尺寸进行缩放_
      await sourcePixelMap.scale(imageScale, imageScale);
     _ // packing压缩_
      compressedImageData = await this.packing(sourcePixelMap, quality, AFTER\_COMPRESS\_FMT);
    } else {
      _// imageScale缩放倍数小于等于0时，没有意义，结束压缩。_
      break;
    }
  }
  this.intelligentCompressionQuality = quality;
  let imageSource = image.createImageSource(compressedImageData);
  let opts: image.DecodingOptions = { editable: true };
  this.showPixelMap = await imageSource.createPixelMap(opts);
  this.savePixelMap = sourcePixelMap;
  this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
  this.showCompressFormat = this.afterCompressFmt;
  _// 可能会存在scale每次缩小倍数设置过大，导致无法压缩到图片压缩目标大小，弹窗提示_
  if (compressedImageData.byteLength > maxCompressedImageSize \* BYTE\_CONVERSION) {
    let uiContext = AppStorage.get('context') as UIContext;
    uiContext.showAlertDialog({
      message: '当前在设置的scale每次缩小倍数条件下，无法压缩到目标大小，建议调低scale每次缩小倍数后再试',
      alignment: DialogAlignment.Center
    });
  }
}

#### \[h2\]方案四：格式转换

通过格式转换实现图片压缩。常见的图片文件格式文件大小对比：

| 
格式

 | 

文件大小

 |
| :-- | :-- |
| 

PNG

 | 

无损压缩格式，文件大小通常比有损压缩格式（如JPEG）要大。

 |
| 

JPG

 | 

有损压缩格式，文件大小通常比PNG小得多。通过调整压缩参数，可以进一步减小文件大小，但会牺牲图像质量。

 |
| 

WEBP

 | 

支持有损和无损压缩，文件大小通常比JPEG和PNG更小。有损压缩模式下，WebP可以在保持较高图像质量的同时，显著减小文件大小。

 |

本示例以JPEG格式转换，示例代码如下：

_/\*\*_
 _\* 格式转换压缩_
 _\*/_
formatConversionMethod(format: string): Promise<\[PixelMap, PixelMap, string, string\]> {
  this.afterCompressFmt = format;
  AppStorage.setOrCreate('convertFormat', format);
  const ALBUM\_PATH: string = this.uris\[0\];
  const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
  let buffer = new ArrayBuffer(fs.statSync(file.fd).size);
  fs.readSync(file.fd, buffer);
  fs.closeSync(file);
  const decodingOptions: image.DecodingOptions = { editable: true };
  const imageSource: image.ImageSource = image.createImageSource(buffer);
  return new Promise<\[PixelMap, PixelMap, string, string\]>((resolve, reject) => {
    imageSource.createPixelMap(decodingOptions).then(async (pixelMap: image.PixelMap) => {
      this.savePixelMap = pixelMap;
      _// packing压缩图片_
      let compressedImageData: ArrayBuffer =
        await this.packing(pixelMap, this.imageQuality, this.afterCompressFmt);
      _// 压缩后的ArrayBuffer数据转PixelMap_
      let imageSource = image.createImageSource(compressedImageData);
      let opts: image.DecodingOptions = { editable: true };
      _// showPixelMap用于显示压缩后的图片_
      this.showPixelMap = await imageSource.createPixelMap(opts);
      _// showCompressFormat用于显示压缩后的图片格式_
      this.showCompressFormat = this.afterCompressFmt;
      _// 显示估算packing压缩后的图片大小。该图片在内存中作为ArrayBuffer数据的压缩后大小，这一数值并不直接等同于该图片在最终保存到相册时的实际文件大小。_
      this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
      this.promptAction.showToast({ message: '格式转换压缩完成！' });
      resolve(\[this.showPixelMap, this.savePixelMap, this.afterCompressionSize, this.afterCompressFmt\]);
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, \`Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.\`);
      reject(err);
    });
  });
}

完整示例代码如下：

Index.ets：

import { emitter } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { PromptAction } from '@kit.ArkUI';
import { CompressionMethod } from './compression';


const SPACE\_SIX: number = 6; _// 组件间距_
const SPACE\_THIRTY: number = 30; _// 组件间距_
const FONT\_WEIGHT: number = 500;
const BYTE\_CONVERSION: number = 1024; _// 字节转换_

@Entry
@Component
struct Index {
  @State beforeCompressImgSize: string = ''; _// 压缩前图片宽高_
  @State afterCompressImgSize: string = ''; _// 压缩后图片宽高_
  @State @Watch('afterCompressChange') afterCompressionSize: string = ''; _// 压缩后的图片大小,单位kb_
  @State outputFormatIndex: number = 0; _// 输出格式初始项，默认原格式_
  @State @Watch('compressFormatChange') showCompressFormat: string = ''; _// 在页面上显示压缩后的图片格式_
  @State showPixelMap: image.PixelMap | undefined = undefined; _// 页面上显示压缩后的图片_
  @State sizeFlag: boolean = false; _// 是否尺寸缩模式_
  @State savePixelMap: image.PixelMap | undefined = undefined; _// 保存压缩后的图片_
  private context: UIContext = this.getUIContext(); _// 获取getPhotoAccessHelper需要的context_
  promptAction: PromptAction = this.getUIContext().getPromptAction();
  private outputFormat: string\[\] = \['原格式', 'PNG', 'JPEG', 'WEBP'\]; _// 图片输出格式_
  private compressionMethod: CompressionMethod = new CompressionMethod();
  @StorageLink('photoCount') selectPhotoCount: number = 0;
  @StorageLink('beforeCompressFmt') beforeCompressFmt: string = '';
  @StorageLink('afterCompressFmt') afterCompressFmt: string = '';
  @StorageLink('beforeCompressByteLength') beforeCompressByteLength1: number = 0;
  @StorageLink('uris') selcteUris: Array<string> = \[\];
  @StorageLink('convertFormat') convertFormat: string = '';

  _/\*\*_
   _\* 压缩后图片大小_
   _\*/_
  afterCompressChange() {
    emitter.emit('ImageCompression0', {
      data: {
        afterCompressionSize: this.afterCompressionSize
      }
    });
  }

  _/\*\*_
   _\* 压缩后图片格式_
   _\*/_
  compressFormatChange() {
    emitter.emit('ImageCompression1', {
      data: {
        showCompressFormat: this.showCompressFormat
      }
    });
  }

  _/\*\*_
   _\* 尺寸压缩_
   _\*/_
  async sizeCompressionMethod(size: number) {
    this.sizeFlag = true;
    AppStorage.setOrCreate('sizeFlag', this.sizeFlag);
    const sizeCompressionMethod = await this.compressionMethod.compressionMethod(size, 100);
    this.showPixelMap = sizeCompressionMethod\[0\];
    this.afterCompressionSize = sizeCompressionMethod\[2\];
    this.showCompressFormat = sizeCompressionMethod\[3\];
  }


 _ /\*\*_
   _\* 质量压缩_
   _\*/_
  async qualityCompressionMethod(quality: number) {
    const qualityCompressionMethod = await this.compressionMethod.compressionMethod(100, quality);
    this.showPixelMap = qualityCompressionMethod\[0\];
    this.savePixelMap = qualityCompressionMethod\[1\];
    this.afterCompressionSize = qualityCompressionMethod\[2\];
    this.showCompressFormat = qualityCompressionMethod\[3\];
  }


 _ /\*\*_
   _\* 显示压缩前和压缩后图片_
   _\*/_
  @Builder
  displayImage() {
    Row() {
      Text('压缩前：')
        .fontWeight(FONT\_WEIGHT)
        .fontSize(16);

      if (this.selectPhotoCount > 0) {
        Text((this.beforeCompressByteLength1 / BYTE\_CONVERSION).toFixed(1))
          .fontWeight(FONT\_WEIGHT)
          .fontSize(20)
          .fontColor('#ff0e9eff');
        Text('kb  格式：')
          .fontSize(16);
        Text(this.beforeCompressFmt.toUpperCase()).fontSize(16);
      }
    }
    .width('100%')
    .padding({
      left: 15,
      right: 15
    })
    .margin({ bottom: 5 });

    Column() {
      if (this.selectPhotoCount > 0) {
        Image(this.selcteUris\[0\])
          .objectFit(ImageFit.Contain)
          .onComplete((event) => {
            if (event) {
              this.beforeCompressImgSize = event?.width.toString() + '\*' + event?.height.toString();
            }
          });
        Text(this.beforeCompressImgSize)
          .fontSize(13)
          .opacity(0.8)
          .margin(10);
      } else {
        Image($r('app.media.image\_compression\_add\_photo')) _// 此处仅为样例，请开发者更换为可用图片_
          .objectFit(ImageFit.None)
          .height(50)
          .margin({ top: 30, bottom: 10 });
        Text('点击添加图片')
          .fontSize(14);
      }
    }
    .onClick(() => {
     _ // 初始化_
      this.afterCompressionSize = '';
      this.outputFormatIndex = 0;
     _ // 拉起图库选择图片_
      this.compressionMethod.selectPhotoFromAlbum();
    })
    .width('70%')
    .height(150)
    .backgroundColor(Color.White)
    .padding(5)
    .margin({ bottom: 15 })
    .borderRadius(5);

    Row() {
      Text('压缩后：')
        .fontWeight(FONT\_WEIGHT)
        .fontSize(16);
      Text(this.afterCompressionSize)
        .fontWeight(FONT\_WEIGHT)
        .fontSize(20)
        .fontColor('#ff0e9eff');
      if (this.afterCompressionSize !== '') {

        Text('kb  格式：')
          .fontSize(16);
        Text(this.showCompressFormat)
          .fontSize(16);
      }
    }
    .width('100%')
    .padding({
      left: 15,
      right: 15
    })
    .margin({ top: 10, bottom: 5 });

    Column() {
      if (this.afterCompressionSize !== '') {
        Image(this.showPixelMap)
          .objectFit(ImageFit.Contain)
          .onComplete((event) => {
            if (event) {
              this.afterCompressImgSize = event?.width.toString() + '\*' + event?.height.toString();
              emitter.emit('ImageCompression3', {
                data: {
                  afterCompressImgSize: this.afterCompressImgSize
                }
              });
            }
          });
        Text(this.afterCompressImgSize)
          .fontSize(13)
          .opacity(0.8)
          .margin(10);
      }
    }
    .width('70%')
    .height(150)
    .backgroundColor(Color.White)
    .padding(5)
    .margin({ bottom: 20 })
    .borderRadius(5);
  }

 _ /\*\*_
   _\* 获取当前选中的输出格式_
   _\* @returns selectFormat 格式名_
   _\*/_
  getSelectFormat(): string {
    let selectFormat: string = '';
    switch (this.outputFormatIndex) {
      case 0: _// 原格式_
        selectFormat = this.beforeCompressFmt.toUpperCase();
        break;
      case 1: _// PNG_
        selectFormat = 'PNG';
        break;
      case 2: _// JPEG_
        selectFormat = 'JPEG';
        break;
      case 3: _// WEBP_
        selectFormat = 'WEBP';
        break;
    }
    return selectFormat;
  }

 _ /\*\*_
   _\* 输出格式菜单_
   _\*/_
  @Builder
  outputFormatMenu() {
    Column() {
      ForEach(this.outputFormat, (item: string, index: number) => {
        Column() {
          Flex({
            direction: FlexDirection.Row,
            justifyContent: FlexAlign.SpaceBetween,
            alignItems: ItemAlign.Center
          }) {
            Text(item)
              .fontSize(15);
            Image($r('app.media.image\_compression\_ok'))_// 此处仅为样例，请开发者更换为可用图片_
              .visibility(this.outputFormatIndex === index ? Visibility.Visible : Visibility.Hidden)
              .width(20)
              .height(20);
          }
          .height(48);

          Divider()
            .visibility(index !== 3 ? Visibility.Visible : Visibility.Hidden)
            .width('100%');
        }
        .width(80)
        .onClick(() => {
          this.outputFormatIndex = index;
          switch (item) {
            case '原格式':
              this.afterCompressFmt = this.beforeCompressFmt;
              AppStorage.setOrCreate('outputCompressFmt', this.afterCompressFmt);
              break;
            case 'PNG':
              this.afterCompressFmt = 'PNG';
              AppStorage.setOrCreate('outputCompressFmt', this.afterCompressFmt);
              break;
            case 'JPEG':
              this.afterCompressFmt = 'JPEG';
              AppStorage.setOrCreate('outputCompressFmt', this.afterCompressFmt);
              break;
            case 'WEBP':
              this.afterCompressFmt = 'WEBP';
              AppStorage.setOrCreate('outputCompressFmt', this.afterCompressFmt);
              break;
          }
        });
      }, (item: string) => item);
    }
    .borderRadius(10)
    .backgroundColor(Color.White)
    .width(100)
    .focusable(false);
  }

  @Builder
  outputFormatSet() {
    Row() {
      Text('输出格式：')
        .fontSize(16)
        .fontWeight(FONT\_WEIGHT);
      Blank();
      Row() {
        Text(this.outputFormat\[this.outputFormatIndex\])
          .fontSize(15)
          .opacity(0.8);

        Image($r('app.media.image\_compression\_spinner'))_// 此处仅为样例，请开发者更换为可用图片_
          .width(22)
          .height(16)
          .opacity(0.5)
          .margin({
            left: 8,
            right: 8
          });
      }
      .id('outputFormat')
      .bindMenu(this.outputFormatMenu(), { placement: Placement.Top });
    }
    .width('100%')
    .height(48)
    .borderRadius(10)
    .backgroundColor(Color.White)
    .padding({
      left: 15,
      right: 15,
      top: 10,
      bottom: 10
    });
  }

 _ /\*\*_
   _\* 保存到相册_
   _\*/_
  @Builder
  compressAndSave() {
    Row({ space: SPACE\_THIRTY }) {
      SaveButton({ text: SaveDescription.SAVE\_IMAGE, buttonType: ButtonType.Capsule })
        .height(40)
        .borderRadius(12)
        .onClick(async () => {
          if (this.afterCompressionSize === '') {
            this.context.showAlertDialog({
              message: '请先压缩',
              alignment: DialogAlignment.Center
            });
            return;
          }
          if (this.showCompressFormat == this.convertFormat || this.showCompressFormat == this.getSelectFormat()) {
            await this.compressionMethod.saveImageToAlbum();

          } else {
            this.context.showAlertDialog({
              message: '请先压缩该输出格式后再试',
              alignment: DialogAlignment.Center
            });
            return;
          }

        });
    };
  }

  build() {
    Column({ space: SPACE\_SIX }) {
      this.displayImage();
     _ // 输出格式_
      this.outputFormatSet();
      Button('质量压缩')
        .onClick(() => {
          if (this.selectPhotoCount <= 0) {
            this.context.showAlertDialog({
              message: '请先添加待压缩的图片',
              alignment: DialogAlignment.Center
            });
            return;
          }
         _ // 质量压缩_
          this.qualityCompressionMethod(50);
          this.promptAction.showToast({ message: '质量压缩完成！' });

        });
      Button('尺寸压缩')
        .onClick(() => {
          if (this.selectPhotoCount <= 0) {
            this.context.showAlertDialog({
              message: '请先添加待压缩的图片',
              alignment: DialogAlignment.Center
            });
            return;
          }
         _ // 尺寸压缩_
          this.sizeCompressionMethod(50);
          this.promptAction.showToast({ message: '尺寸压缩完成！' });
        });
      Button('智能压缩')
        .onClick(async () => {
          if (this.selectPhotoCount <= 0) {
            this.context.showAlertDialog({
              message: '请先添加待压缩的图片',
              alignment: DialogAlignment.Center
            });
            return;
          }
        _  // 智能压缩_
          const intelligentCompressionMethodCallBack =
            await this.compressionMethod.intelligentCompressionMethod(true, 600, 10, 80);
          this.showPixelMap = intelligentCompressionMethodCallBack\[0\];
          this.savePixelMap = intelligentCompressionMethodCallBack\[1\];
          this.afterCompressionSize = intelligentCompressionMethodCallBack\[2\];
          this.showCompressFormat = intelligentCompressionMethodCallBack\[3\];
        });

      Button('格式转换')
        .align(Alignment.Center)
        .onClick(async () => {
          if (this.selectPhotoCount <= 0) {
            this.context.showAlertDialog({
              message: '请先添加待压缩的图片',
              alignment: DialogAlignment.Center
            });
            return;
          }
        _  // 传入需要转换的格式_
          const formatConversionMethod = await this.compressionMethod.formatConversionMethod('JPEG');
          this.showPixelMap = formatConversionMethod\[0\];
          this.savePixelMap = formatConversionMethod\[1\];
          this.afterCompressionSize = formatConversionMethod\[2\];
          this.showCompressFormat = formatConversionMethod\[3\];
        });
    _  // 保存到相册_
      this.compressAndSave();
    }
    .padding({
      top: 10,
      left: 20,
      right: 20,
      bottom: 10
    })
    .backgroundColor('#99e3e3e3')
    .height('100%')
    .width('100%')
    .expandSafeArea(\[SafeAreaType.SYSTEM\], \[SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM\]);
  }
}

compression.ets：

import { BusinessError } from '@kit.BasicServicesKit';
import { image } from '@kit.ImageKit';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { PromptAction } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';

const TAG = 'IMAGE\_COMPRESSION';
const BYTE\_CONVERSION: number = 1024; _// 字节转换_
const IMAGE\_QUALITY\_ZERO: number = 0; _// 图片质量0_
const IMAGE\_QUALITY\_HUNDRED: number = 100; _// 图片质量100_

export class CompressionMethod {
  private uris: Array<string> = \[\]; _// 保存图库选择的结果uri_
  private photoCount: number = 0; _// 选择图片的个数_
  private beforeCompressFmt: string = ''; _// 压缩前图片格式_
  private afterCompressFmt: string = ''; _// 压缩后图片格式_
  private beforeCompressByteLength: number = 0; _// 压缩前图片字节长度_
  private sizeFlag: boolean = false; _// 是否尺寸缩模式_
  private savePixelMap: image.PixelMap | undefined = undefined; _// 保存压缩后的图片_
  private imageQuality: number = 100; _// 图片压缩质量_
  private showPixelMap: image.PixelMap | undefined = undefined; _// 页面上显示压缩后的图片_
  private afterCompressionSize: string = ''; _// 压缩后的图片大小,单位kb_
  private showCompressFormat: string = ''; _// 在页面上显示压缩后的图片格式_
  private qualityCompression: boolean = true; _// 是否质量压缩模式_
  private intelligentCompressionQuality: number = 0; _// 自动模式保存图片的质量_
  promptAction: PromptAction = (AppStorage.get('context') as UIContext).getPromptAction();

 _ /\*\*_
   _\* 拉起图库选择图片_
   _\*/_
  async selectPhotoFromAlbum() {
   _ // 创建图库选项实例_
    const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
    _// 设置选择的媒体文件类型为Image_
    photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE\_TYPE;
   _ // 设置选择媒体文件的最大数目_
    photoSelectOptions.maxSelectNumber = 1;
    _// 创建图库选择器实例_
    const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
    _// 调用photoViewPicker.select()接口拉起图库界面进行图片选择，图片选择成功后，返回photoSelectResult结果集。_
    photoViewPicker.select(photoSelectOptions).then((photoSelectResult) => {
      _// select返回的uri权限是只读权限，需要将uri写入全局变量@State中即可进行读取文件数据操作。_
      this.uris = photoSelectResult.photoUris;
      AppStorage.setOrCreate('uris', this.uris);
      this.photoCount = this.uris.length;
      AppStorage.setOrCreate('photoCount', this.photoCount);
      if (this.photoCount > 0) {
        const ALBUM\_PATH: string = photoSelectResult.photoUris\[0\];
        _// 找到最后一个点（.）的索引位置_
        let lastDotIndex = ALBUM\_PATH.lastIndexOf('.');
        _// 使用slice方法从最后一个点之后的位置开始截取字符串到末尾_
        this.beforeCompressFmt =
          ALBUM\_PATH.slice(lastDotIndex + 1) === 'jpg' ? 'jpeg' : ALBUM\_PATH.slice(lastDotIndex + 1);
        AppStorage.setOrCreate('beforeCompressFmt', this.beforeCompressFmt);
        this.afterCompressFmt = this.beforeCompressFmt.toUpperCase();
        AppStorage.setOrCreate('afterCompressFmt', this.afterCompressFmt);
        _// 读取选择图片的buffer_
        const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
        _// 获取选择图片的字节长度_
        this.beforeCompressByteLength = fs.statSync(file.fd).size;
        AppStorage.setOrCreate('beforeCompressByteLength', this.beforeCompressByteLength);
        fs.closeSync(file);
      }
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, \`PhotoViewPicker.select failed :, error code: ${err.code}, message: ${err.message}.\`);
    });
  }

  _/\*\*_
   _\* packing压缩_
   _\* @param sourcePixelMap 原始待压缩图片的PixelMap_
   _\* @param imageQuality 图片质量参数_
   _\* @param packingFormat 目标格式。当前只支持"image/jpeg"、"image/webp" 和 "image/png"_
   _\* @returns data 返回压缩后的图片数据_
   _\*/_
  async packing(sourcePixelMap: image.PixelMap, imageQuality: number, packingFormat: string): Promise<ArrayBuffer> {
    const imagePackerApi = image.createImagePacker();
    const packOpts: image.PackingOption = { format: 'image/' + packingFormat, quality: imageQuality };
    const data: ArrayBuffer = await imagePackerApi.packToData(sourcePixelMap, packOpts);
    return data;
  }

  _/\*\*_
   _\* 压缩_
   _\*/_
  async compressionMethod(imageScaleVal: number,
    imageQualityVal: number): Promise<\[PixelMap, PixelMap, string, string\]> {
    const ALBUM\_PATH: string = this.uris\[0\];
    const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
    let buffer = new ArrayBuffer(fs.statSync(file.fd).size);
    fs.readSync(file.fd, buffer);
    fs.closeSync(file);
    const decodingOptions: image.DecodingOptions = { editable: true };
    const imageSource: image.ImageSource = image.createImageSource(buffer);
    return new Promise<\[PixelMap, PixelMap, string, string\]>((resolve) => {
      imageSource.createPixelMap(decodingOptions).then(async (originalPixelMap: image.PixelMap) => {
        if (AppStorage.get('sizeFlag') != undefined) {
          this.sizeFlag = AppStorage.get('sizeFlag')!;
        }
        if (this.sizeFlag) {
          _// 使用scale对图片进行缩放。入参分别为图片宽高的缩放倍数_
          await originalPixelMap.scale(imageScaleVal / 100, imageScaleVal / 100);
          _// savePixelMap用于把压缩后的图片保存到图库时使用。由于保存图片时调用的packToFile内部会做类似packing的处理，所以这里只保存scale缩放尺寸后的PixelMap。_
          this.savePixelMap = originalPixelMap;
          this.imageQuality = imageScaleVal;
        }
        _// savePixelMap用于把压缩后的图片保存到图库时使用。_
        this.savePixelMap = originalPixelMap;
        if (AppStorage.get('outputCompressFmt') != undefined) {
          this.afterCompressFmt = AppStorage.get('outputCompressFmt')!;
        }

        _// packing压缩图片_
        let compressedImageData: ArrayBuffer =
          await this.packing(originalPixelMap, imageQualityVal, this.afterCompressFmt);
        _// 压缩后的ArrayBuffer数据转PixelMap_
        let imageSource = image.createImageSource(compressedImageData);
        let opts: image.DecodingOptions = { editable: true };
        _// showPixelMap用于显示压缩后的图片_
        const pixelMap: image.PixelMap = await imageSource.createPixelMap(opts);
        this.showPixelMap = pixelMap;
        _// showCompressFormat用于显示压缩后的图片格式_
        this.showCompressFormat = this.afterCompressFmt;
       _ // 显示估算packing压缩后的图片大小。该图片在内存中作为ArrayBuffer数据的压缩后大小，这一数值并不直接等同于该图片在最终保存到相册时的实际文件大小。_
        this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
        resolve(\[this.showPixelMap, this.savePixelMap, this.afterCompressionSize, this.afterCompressFmt\]);
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, \`Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.\`);
      });

    });
  }

  _/\*\*_
   _\* packing二分方式循环压缩_
   _\* @param sourcePixelMap 原始待压缩图片的PixelMap_
   _\* @param compressedImageData 图片压缩的ArrayBuffer_
   _\* @param maxCompressedImageByte 压缩目标图像字节长度_
   _\* @param minBisectUnit 最小二分单位_
   _\*/_
  async packingImage(sourcePixelMap: image.PixelMap, compressedImageData: ArrayBuffer, maxCompressedImageByte: number,
    minBisectUnit: number) {
    let saveImageQuality: number = 0;
    const DICHOTOMY\_ACCURACY = minBisectUnit;
    _// 图片质量参数范围为0-100，这里以minBisectUnit为最小二分单位创建用于packing二分图片质量参数的数组。_
    const packingArray: number\[\] = \[\];
    _// 性能知识点: 如果对图片压缩质量要求不高，建议调高minBisectUnit（对应‘packing最小二分单位’），减少循环，提升packing压缩性能。_
    for (let i = 0; i <= 100; i += DICHOTOMY\_ACCURACY) {
      packingArray.push(i);
    }
    let left = 0; _// 定义二分搜索范围的左边界_
    let right = packingArray.length - 1; _// 定义二分搜索范围的右边界_
    const AFTER\_COMPRESS\_FMT = this.afterCompressFmt;
   _ // 二分压缩图片_
    while (left <= right) {
      const mid = Math.floor((left + right) / 2); _// 定义二分搜索范围的中间位置_
      saveImageQuality = packingArray\[mid\]; _// 获取二分中间位置的图片质量值_
     _ // 根据传入的图片质量参数进行packing压缩，返回压缩后的图片文件流数据。_
      compressedImageData = await this.packing(sourcePixelMap, saveImageQuality, AFTER\_COMPRESS\_FMT);
     _ // 判断查找一个尽可能接近但不超过压缩目标的压缩大小_
      if (compressedImageData.byteLength <= maxCompressedImageByte) {
       _ // 二分目标值在右半边，继续在更高的图片质量参数（即mid + 1）中搜索_
        left = mid + 1;
       _ // 判断mid是否已经二分到最后，如果二分完了，退出_
        if (mid === packingArray.length - 1) {
          break;
        }
       _ // 获取下一次二分的图片质量参数（mid+1）压缩的图片文件流数据_
        compressedImageData = await this.packing(sourcePixelMap, packingArray\[mid + 1\], AFTER\_COMPRESS\_FMT);
       _ // 判断用下一次图片质量参数（mid+1）压缩的图片大小是否大于指定图片的压缩目标大小。如果大于，说明当前图片质量参数（mid）压缩出来的_
        _// 图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid，得到最终目标图片压缩数据。_
        if (compressedImageData.byteLength > maxCompressedImageByte) {
          compressedImageData = await this.packing(sourcePixelMap, packingArray\[mid\], AFTER\_COMPRESS\_FMT);
          break;
        }
      } else {
        _// 目标值不在当前范围的右半部分，将搜索范围的右边界向左移动，以缩小搜索范围并继续在下一次迭代中查找左半部分。_
        right = mid - 1;
      }
    }
    _// 保存最终压缩的图片质量，用于保存到图库时使用_
    this.intelligentCompressionQuality = saveImageQuality;
   _ // ArrayBuffer转PixelMap，并保存相关数据_
    let imageSource = image.createImageSource(compressedImageData);
    let opts: image.DecodingOptions = { editable: true };
    this.showPixelMap = await imageSource.createPixelMap(opts);
    this.savePixelMap = sourcePixelMap;
    this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
  }

  _/\*\*_
   _\* 优先压缩图片尺寸_
   _\* @param sourcePixelMap 原始待压缩图片的PixelMap_
   _\* @param maxCompressedImageSize 指定图片的压缩目标大小_
   _\* @param quality_
   _\*/_
  async scalePriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number, quality: number) {
    let compressedImageData: ArrayBuffer = await this.packing(sourcePixelMap, quality, this.afterCompressFmt);
    _// 如果调整过最低图片质量，先计算最低图片质量是否满足要求。如果设定的最低图片质量已经满足目标大小，则直接返回_
    if (quality !== IMAGE\_QUALITY\_HUNDRED &&
      (compressedImageData.byteLength <= maxCompressedImageSize \* BYTE\_CONVERSION)) {
     _ // 保存最终压缩的图片质量，用于保存到图库时使用_
      this.intelligentCompressionQuality = quality;
      _// ArrayBuffer转PixelMap，并保存图片相关数据_
      let imageSource = image.createImageSource(compressedImageData);
      let opts: image.DecodingOptions = { editable: true };
      this.showPixelMap = await imageSource.createPixelMap(opts);
      this.savePixelMap = sourcePixelMap;
      this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
     _ // 更新显示压缩后的图片格式_
      this.showCompressFormat = this.afterCompressFmt;
      return;
    }
   _ // scale压缩图片尺寸。采用while循环每次递减0.4，最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。_
    let imageScale = 1; _// 定义图片宽高的缩放倍数，1表示原比例。_
    const REDUCE\_SCALE = 0.5;
    const AFTER\_COMPRESS\_FMT = this.afterCompressFmt;
   _ // 判断压缩后的图片大小是否大于指定图片的压缩目标大小，如果大于，继续降低缩放倍数压缩。_
    while (compressedImageData.byteLength > maxCompressedImageSize \* BYTE\_CONVERSION) {
      if (imageScale > 0) {
     _   // 性能知识点: 由于scale会直接修改图片PixelMap数据，所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片，_
        _// 来查找确定最适合的缩放倍数。如果对图片压缩质量要求不高，建议调高每次递减的缩放倍数，减少循环，提升scale压缩性能。_
        imageScale = imageScale - REDUCE\_SCALE; _// 每次缩放倍数减_
       _ // 使用scale对图片尺寸进行缩放_
        await sourcePixelMap.scale(imageScale, imageScale);
      _  // packing压缩_
        compressedImageData = await this.packing(sourcePixelMap, quality, AFTER\_COMPRESS\_FMT);
      } else {
       _ // imageScale缩放倍数小于等于0时，没有意义，结束压缩。_
        break;
      }
    }
    this.intelligentCompressionQuality = quality;
    let imageSource = image.createImageSource(compressedImageData);
    let opts: image.DecodingOptions = { editable: true };
    this.showPixelMap = await imageSource.createPixelMap(opts);
    this.savePixelMap = sourcePixelMap;
    this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
    this.showCompressFormat = this.afterCompressFmt;
  _  // 可能会存在scale每次缩小倍数设置过大，导致无法压缩到图片压缩目标大小，弹窗提示_
    if (compressedImageData.byteLength > maxCompressedImageSize \* BYTE\_CONVERSION) {
      let uiContext = AppStorage.get('context') as UIContext;
      uiContext.showAlertDialog({
        message: '当前在设置的scale每次缩小倍数条件下，无法压缩到目标大小，建议调低scale每次缩小倍数后再试',
        alignment: DialogAlignment.Center
      });
    }
  }

  _/\*\*_
   _\* 优先压缩图片质量_
   _\* @param sourcePixelMap 原始待压缩图片的PixelMap_
   _\* @param maxCompressedImageSize 指定图片的压缩目标大小_
   _\*/_
  async qualityPriorityCompress(sourcePixelMap: image.PixelMap, maxCompressedImageSize: number, minBisectUnit: number) {
    let compressedImageData: ArrayBuffer =
      await this.packing(sourcePixelMap, IMAGE\_QUALITY\_ZERO, this.afterCompressFmt);
 _   // 先判断图片质量参数设置最低0能否满足目标大小。如果能满足目标大小，则直接使用packing二分图片质量。如果不满足，则质量参数固定为0，进行scale尺寸压缩_
    if (compressedImageData.byteLength <= maxCompressedImageSize \* BYTE\_CONVERSION) {
    _  // 满足目标大小，直接使用packing二分_
      await this.packingImage(sourcePixelMap, compressedImageData, maxCompressedImageSize \* BYTE\_CONVERSION,
        minBisectUnit);
    } else {
   _   // 不满足目标大小，质量参数设置为0，再进行scale尺寸压缩_
      await this.scalePriorityCompress(sourcePixelMap, maxCompressedImageSize, IMAGE\_QUALITY\_ZERO);
    }
  _  // 更新显示压缩后的图片格式_
    this.showCompressFormat = this.afterCompressFmt;
  }

  _/\*\*_
   _\* 图片压缩_
   _\* @param sourcePixelMap：原始待压缩图片的PixelMap对象_
   _\* @param maxCompressedImageSize：指定图片的压缩目标大小，单位kb_
   _\*/_
  async compressedImage(isqualityCompression: boolean, sourcePixelMap: image.PixelMap, maxCompressedImageSize: number,
    minBisectUnit: number, minQuality: number): Promise<void> {
    this.qualityCompression = isqualityCompression;
  _  // 判断是优先压缩质量还是优先压缩尺寸_
    if (isqualityCompression) {
    _  // 优先压缩质量_
      await this.qualityPriorityCompress(sourcePixelMap, maxCompressedImageSize, minBisectUnit);
    } else {
    _  // 优先压缩尺寸_
      await this.scalePriorityCompress(sourcePixelMap, maxCompressedImageSize, minQuality);
    }
  }

 _ /\*\*_
   _\* 智能压缩_
   _\* @param qualityCompression：是否质量优先_
   _\* @param maxCompressedImageSize 指定图片压缩目标大小，单位kb_
   _\* @param minBisectUnit packing最小二分单位_
   _\* @param minQuality 最低图片质量_
   _\*/_
  intelligentCompressionMethod(qualityCompression: boolean, maxCompressedImageSize: number, minBisectUnit: number,
    minQuality: number): Promise<\[PixelMap, PixelMap, string, string\]> {
   _ // 获取图库中选择的图片路径_
    const ALBUM\_PATH: string = this.uris\[0\];
    const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
    let buffer: ArrayBuffer = new ArrayBuffer(fs.statSync(file.fd).size);
   _ // 读取图片到buffer_
    fs.readSync(file.fd, buffer);
    fs.closeSync(file);
   _ // 通过缓冲区创建图片源实例_
    const imageSource: image.ImageSource = image.createImageSource(buffer);
   _ // 设置解码参数DecodingOptions，解码获取PixelMap图片对象。_
    const decodingOptions: image.DecodingOptions = {
      editable: true, _// 是否可编辑。当取值为false时，图片不可二次编辑，如crop等操作将失败。_
    };
    return new Promise<\[PixelMap, PixelMap, string, string\]>((resolve, reject) => {
      imageSource.createPixelMap(decodingOptions).then(async (originalPixelMap: image.PixelMap) => {
        try {
          if (AppStorage.get('outputCompressFmt') != undefined) {
            this.afterCompressFmt = AppStorage.get('outputCompressFmt')!;
          }
        _  // 压缩图片_
          await this.compressedImage(qualityCompression, originalPixelMap, maxCompressedImageSize, minBisectUnit,
            minQuality);
          this.promptAction.showToast({ message: '智能压缩完成！' });
          resolve(\[this.showPixelMap!, this.savePixelMap!, this.afterCompressionSize, this.showCompressFormat\]);
        } catch (err) {
          hilog.error(0x0000, TAG, \`Failed to compress image, error code: ${err.code}, message: ${err.message}.\`);
          reject(err);
        }
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, \`Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.\`);
        reject(err);
      });
    });
  }


  _/\*\*_
   _\* 格式转换压缩_
   _\*/_
  formatConversionMethod(format: string): Promise<\[PixelMap, PixelMap, string, string\]> {
    this.afterCompressFmt = format;
    AppStorage.setOrCreate('convertFormat', format);
    const ALBUM\_PATH: string = this.uris\[0\];
    const file = fs.openSync(ALBUM\_PATH, fs.OpenMode.READ\_ONLY);
    let buffer = new ArrayBuffer(fs.statSync(file.fd).size);
    fs.readSync(file.fd, buffer);
    fs.closeSync(file);
    const decodingOptions: image.DecodingOptions = { editable: true };
    const imageSource: image.ImageSource = image.createImageSource(buffer);
    return new Promise<\[PixelMap, PixelMap, string, string\]>((resolve, reject) => {
      imageSource.createPixelMap(decodingOptions).then(async (pixelMap: image.PixelMap) => {
        this.savePixelMap = pixelMap;
      _  // packing压缩图片_
        let compressedImageData: ArrayBuffer =
          await this.packing(pixelMap, this.imageQuality, this.afterCompressFmt);
      _  // 压缩后的ArrayBuffer数据转PixelMap_
        let imageSource = image.createImageSource(compressedImageData);
        let opts: image.DecodingOptions = { editable: true };
      _  // showPixelMap用于显示压缩后的图片_
        this.showPixelMap = await imageSource.createPixelMap(opts);
      _  // showCompressFormat用于显示压缩后的图片格式_
        this.showCompressFormat = this.afterCompressFmt;
     _   // 显示估算packing压缩后的图片大小。该图片在内存中作为ArrayBuffer数据的压缩后大小，这一数值并不直接等同于该图片在最终保存到相册时的实际文件大小。_
        this.afterCompressionSize = (compressedImageData.byteLength / BYTE\_CONVERSION).toFixed(1);
        this.promptAction.showToast({ message: '格式转换压缩完成！' });
        resolve(\[this.showPixelMap, this.savePixelMap, this.afterCompressionSize, this.afterCompressFmt\]);
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, \`Failed to create PixelMap, error code: ${err.code}, message: ${err.message}.\`);
        reject(err);
      });
    });
  }

  _/\*\*_
   _\* 保存图片到图库_
   _\* @returns_
   _\*/_
  async saveImageToAlbum(): Promise<void> {
   _ // 获取相册管理模块的实例_
    const HELPER =
      photoAccessHelper.getPhotoAccessHelper((AppStorage.get('context') as UIContext).getHostContext() as common.UIAbilityContext);
 _   // 指定待创建的文件类型、后缀和创建选项，创建图片资源_
    const URI = await HELPER.createAsset(photoAccessHelper.PhotoType.IMAGE, this.afterCompressFmt);
    let file = await fs.open(URI, fs.OpenMode.READ\_WRITE | fs.OpenMode.CREATE);
    let imagePacker = image.createImagePacker();
    let packOpts: image.PackingOption = {
      format: 'image/' + this.afterCompressFmt,
      quality: this.qualityCompression ? this.intelligentCompressionQuality : this.imageQuality
    };
  _  // 指定打包参数，将PixelMap图片源编码后直接打包进文件_
    imagePacker.packToFile(this.savePixelMap, file.fd, packOpts, async (err: BusinessError) => {
      if (err) {
        hilog.error(0x0000, TAG, \`Failed to pack the image to file, error code: ${err.code}, message: ${err.message}.\`);
      } else {
        try {
          this.promptAction.showToast({ message: '已保存至相册！' });
        } catch (error) {
          console.error(\`showToast args error is ${error}\`);
        }
      }
      await fs.close(file.fd).catch(() => {
        console.info('close file succeed');
      }).finally(() => {
        imagePacker.release();
      });
    });
  }
}

EntryAbility.ets：

import { ConfigurationConstant, UIAbility} from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';

const DOMAIN = 0x0000;

export default class EntryAbility extends UIAbility {
  onCreate(): void {
    try {
      this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR\_MODE\_NOT\_SET);
    } catch (err) {
      hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err));
    }
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
  _  // Main window is created, set main page for this ability_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

    windowStage.loadContent('pages/Index', (err) => {
      let context = windowStage.getMainWindowSync().getUIContext();
      AppStorage.setOrCreate('context', context);
      if (err.code) {
        hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
        return;
      }
      hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
    });
  }

  onWindowStageDestroy(): void {
  _  // Main window is destroyed, release UI related resources_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
  _  // Ability has brought to foreground_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
  _  // Ability has back to background_
    hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
  }
};

#### 总结

HarmonyOS的图片处理生态，以PixelMap为统一数据核心，以ImagePacker和ImageSource为关键接口，构建了一个从“存储”到“内存”再到“渲染”的全链路能力矩阵。开发者可以参考以下压缩方式选择矩阵表根据自己的需求选择不同的压缩方案。

| 
压缩方式

 | 

适用场景

 | 

目标文件大小

 | 

质量要求

 | 

推荐参数

 |
| :-- | :-- | :-- | :-- | :-- |
| 

质量压缩

 | 

社交分享、即时通讯

 | 

中等 (100-500KB)

 | 

中等

 | 

quality: 70-80

 |
| 

尺寸压缩

 | 

头像、缩略图、列表

 | 

小 (<100KB)

 | 

中低

 | 

maxSize: 200-800px

 |
| 

智能压缩

 | 

上传限制、存储优化

 | 

精确控制

 | 

自适应

 | 

targetKB: 200

 |
| 

格式转换

 | 

现代App、Web应用

 | 

中等

 | 

高（同等质量）

 | 

quality: 75-85

 |