鸿蒙图片压缩模糊的优化方案

鸿蒙其实提供了两种图片压缩方式

1、尺寸压缩:scale/scaleSync

说明:根据输入的宽高的缩放倍数对图片进行缩放。

注意:

建议宽高的缩放倍数取非负数,否则会产生翻转效果。

宽高的缩放倍数 = 缩放后的图片宽高 / 缩放前的图片宽高。

2、质量压缩:packing

说明:将图像压缩或重新编码。

注意:

仅对JPEG图片和HEIF图片生效,WebP、PNG等图片均为无损编码无法压缩。

因为jpeg不支持透明通道,若使用带透明通道的数据编码jpeg格式,透明色将变为黑色。


尺寸压缩:


//图片压缩

scaleImage(images: image.PixelMap, scaleSize: number | null): Promise<image.PixelMap> {

  return new Promise(async (resolve, reject) => {

    if (!scaleSize) {

      scaleSize = 500;

    }

    try {

      let  minImageData = await this.scaleSize(images,scaleSize)

           let imageSource = image.createImageSource(minImageData);

      let opts: image.DecodingOptions = { editable: true };

      let showPixelMap = await imageSource.createPixelMap(opts);

      resolve(showPixelMap);

    }

    catch (errCode) {

      reject(errCode);

    }

   })

}


/**

  * 压缩图片尺寸  * @param sourcePixelMap 原始待压缩图片的PixelMap

  * @param maxCompressedImageByte 压缩目标图像字节长度  * @param quality 质量参数0~100

*/

async scaleSize(sourcePixelMap: image.PixelMap, maxCompressedImageByte: number): Promise<ArrayBuffer>{

  let imageScale = 1; // 定义图片宽高的缩放倍数,1表示原比例。

  let imageData = await this.packing(sourcePixelMap,100)

  while (imageData.byteLength / 1024 > maxCompressedImageByte ){

    if (imageScale > 0) {

      if (imageData.byteLength / 1024 > maxCompressedImageByte * 2) {

        imageScale -= 0.5;

      }else {

        imageScale -= 0.1;

      }

      sourcePixelMap.scaleSync(imageScale, imageScale)

      imageData = await this.packing(sourcePixelMap,quality)

    }else {

      break;

    }

}

  return imageData;

}


/**

* packing压缩 只有“image/jpeg"有效 * @param sourcePixelMap 原始待压缩图片的PixelMap

* @param imageQuality 图片质量参数,只针对“image/jpeg"有效 * @param packingFormat 目标格式。当前只支持"image/jpeg"、"image/webp" 和"image/png"

* @returns data 返回压缩后的图片数据*/

async packing(sourcePixelMap: image.PixelMap, imageQuality: number ): Promise<ArrayBuffer> {

  const imagePackerApi = image.createImagePacker();

  const packOpts: image.PackingOption = { format: 'image/' + 'jpeg', quality: imageQuality };

  const data: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);

  return data;

}


实际看下效果,我使用原图(3072 X 4096 8M)压缩到500k看下效果:


原图


使用尺寸压缩500k后的图片效果:


尺寸压缩500K


发现尺寸压缩后图片的质量非常差,图片模糊文字基本已经无法识别,这时候看看图片的参数(666 x 888 488 kB),这样失真的图片肯定无法使用。

再试试质量压缩的效果:

这里我们先调用this.packing()将图片压缩到最小质量(this.minQuality是设置的最小质量值,取值区间在[0,100],如果太小图片辨识图可能会被破坏,这里我取最小值为15),然后判断最小质量压缩的大小minImageData.byteLength与预期大小scaleSize比较:

1、如果minImageData.byteLength/ 1024比scaleSize小,我们只需要压缩质量就能达到想要的大小。

        调用this.packingImage(),使用二分法算法找出最接近scaleSize大小的压缩比例并压缩图片。

2、如果minImageData.byteLength/ 1024比scaleSize还大,我们就需要使用this.packing()将质量压缩到最小,然后再调用尺寸压缩,最大程度保障图片清晰度。

      调用this.scaleSize()找出最接近scaleSize大小的压缩比例并压缩图片,这里的this.scaleSize()和上面的方法有所差别,这里显示将图片使用质量压缩到最后后再去做尺寸压缩

我们看看最终的效果


质量压缩500k

看两个最终压缩后的图片效果,差别相当明显,使用质量压缩的图片只是内存大小减小尺寸没有变化,图片内容清晰可见,这样就完美解决压缩图片的问题

具体实现如下:

//图片压缩

//@scaleSize 预期大小 单位kB

scaleImage(images: image.PixelMap, scaleSize: number | null): Promise<image.PixelMap> {

  return new Promise(async (resolve, reject) => {

    if (!scaleSize) {

      scaleSize = 500;

    }

    try {

//先取图片质量压缩到最小时图片的大小

      let  minImageData = await this.packing(images,this.minQuality)

      if (minImageData.byteLength / 1024 > scaleSize) {

//质量压缩到最小还无法满足,就需要进行尺寸压缩

        minImageData = await this.scaleSize(images,scaleSize,this.minQuality)

      }else {

//使用算法计算出最接近scaleSize的压缩图片

        minImageData=  await this.packingImage(images,scaleSize,this.minQuality)

      }

      let imageSource = image.createImageSource(minImageData);

      let opts: image.DecodingOptions = { editable: true };

      let showPixelMap = await imageSource.createPixelMap(opts);

      resolve(showPixelMap);

    }

    catch (errCode) {

      reject(errCode);

    }

  })

}


/**

  * 压缩图片尺寸  * @param sourcePixelMap 原始待压缩图片的PixelMap

  * @param maxCompressedImageByte 压缩目标图像字节长度  * @param quality 质量参数0~100

*/

async scaleSize(sourcePixelMap: image.PixelMap, maxCompressedImageByte: number,quality: number): Promise<ArrayBuffer>{

  let imageScale = 1; // 定义图片宽高的缩放倍数,1表示原比例。

  let imageData = await this.packing(sourcePixelMap,quality)//获取最小质量压缩后的图

  if (imageData.byteLength / 1024 <= maxCompressedImageByte + 100) {

    return imageData;

  }

  while (imageData.byteLength / 1024 > maxCompressedImageByte + 100){

    if (imageScale > 0) {

      if (imageData.byteLength / 1024 > maxCompressedImageByte * 2) {

        imageScale -= 0.5;

      }else {

        imageScale -= 0.1;

      }

      sourcePixelMap.scaleSync(imageScale, imageScale)

      imageData = await this.packing(sourcePixelMap,quality)

    }else {

      break;

    }

}

  return imageData;

}

  /**

  * packing二分方式循环压缩  * @param sourcePixelMap 原始待压缩图片的PixelMap

  * @param maxCompressedImageByte 压缩目标图像字节长度*/

  async packingImage(sourcePixelMap: image.PixelMap, maxCompressedImageByte: number ,minQuality: number) : Promise<ArrayBuffer>{

    let imageQuality: number = 0;//图片质量

    const DICHOTOMY_ACCURACY = 5;//二分法精度,越小越精确

    let  compressedImageData = await this.packing(sourcePixelMap, 100);

    // 图片质量参数范围为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; // 定义二分搜索范围的右边界

    // 二分压缩图片

    while (left <= right) {

      const mid = Math.floor((left + right) / 2); // 定义二分搜索范围的中间位置

      imageQuality = packingArray[mid]; // 获取二分中间位置的图片质量值

      // 根据传入的图片质量参数进行packing压缩,返回压缩后的图片文件流数据。

      compressedImageData = await this.packing(sourcePixelMap, imageQuality);

      // 判断查找一个尽可能接近但不超过压缩目标的压缩大小

      if (compressedImageData.byteLength/1024 <= maxCompressedImageByte) {

        // 二分目标值在右半边,继续在更高的图片质量参数(即mid + 1)中搜索

        left = mid + 1;

        // 判断mid是否已经二分到最后,如果二分完了,退出

        if (mid === packingArray.length - 1) {

          break;

        }

        // 获取下一次二分的图片质量参数(mid+1)压缩的图片文件流数据

        compressedImageData = await this.packing(sourcePixelMap, packingArray[mid + 1]);

        // 判断用下一次图片质量参数(mid+1)压缩的图片大小是否大于指定图片的压缩目标大小。如果大于,说明当前图片质量参数(mid)压缩出来的

        // 图片大小最接近指定图片的压缩目标大小。传入当前图片质量参数mid,得到最终目标图片压缩数据。

        if (compressedImageData.byteLength/1024 > maxCompressedImageByte) {

          compressedImageData = await this.packing(sourcePixelMap, packingArray[mid]);

          break;

        }

      } else {

        // 目标值不在当前范围的右半部分,将搜索范围的右边界向左移动,以缩小搜索范围并继续在下一次迭代中查找左半部分。

        right = mid - 1;

      }

}

    return compressedImageData;

  }

  /**

  * packing压缩 只有“image/jpeg"有效  * @param sourcePixelMap 原始待压缩图片的PixelMap

  * @param imageQuality 图片质量参数,只针对“image/jpeg"有效  * @param packingFormat 目标格式。当前只支持"image/jpeg"、"image/webp" 和"image/png"

  * @returns data 返回压缩后的图片数据*/

  async packing(sourcePixelMap: image.PixelMap, imageQuality: number ): Promise<ArrayBuffer> {

    const imagePackerApi = image.createImagePacker();

    const packOpts: image.PackingOption = { format: 'image/' + 'jpeg', quality: imageQuality };

    const data: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);

    return data;

  }


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容