鸿蒙 图片压缩

//压缩封装

ysImg.ets

import { image } from '@kit.ImageKit';

import { fileIo } from '@kit.CoreFileKit';

import { util } from '@kit.ArkTS';

export class CompressedImageInfo {

  imageUri: string = ""; // 压缩后图片保存位置的uri

  name: string = ""; // 压缩后图片名称

  imageByteLength: number = 0; // 压缩后图片字节长度

  originalPath: string = ""; // 原路径

}

export async function imageToPixelMap(uri: string): Promise<image.PixelMap> {

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

    try {

      // 解码成PixelMap

      const imageSource = image.createImageSource(uri);

      const decodingOptions: image.DecodingOptions = {

        editable: true,

        desiredPixelFormat: 3,

      }

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

        resolve(pixelMap)

      })

    } catch (error) {

      reject(error);

    }

  });

}

/**

* 图片拷贝,保存 * @param uri  原始图片路径 * @returns 拷贝后的沙箱路径*/

export async function imageCopy(uri: string): Promise<CompressedImageInfo> {

  const saveDir = getContext().cacheDir //存储的文件目录

  const file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY)

  const name = util.generateRandomUUID() + ".jpg"

  const fileDir = saveDir + "/" + name

  fileIo.copyFileSync(file.fd, fileDir)

  const photoSize = fileIo.statSync(file.fd).size; // 获取图片大小 单位:字节

  fileIo.closeSync(file.fd)

  let compressedImageInfo: CompressedImageInfo = new CompressedImageInfo();

  compressedImageInfo.imageUri = fileDir;

  compressedImageInfo.name = name;

  compressedImageInfo.imageByteLength = photoSize;

  return compressedImageInfo;

}

/**

* 图片压缩,保存 * @param sourcePixelMap:原始待压缩图片的PixelMap对象 * @param maxCompressedImageSize:指定图片的压缩目标大小,单位kb

* @returns compressedImageInfo:返回最终压缩后的图片信息*/

export async function compressedImage(filePath: string,

  maxCompressedImageSize: number): Promise<CompressedImageInfo> {

  let compress = await imageCopy(filePath)

  let sourcePixelMap = await imageToPixelMap(compress.imageUri)

  // 创建图像编码ImagePacker对象

  const imagePackerApi = image.createImagePacker();

  const IMAGE_QUALITY = 0;

  const packOpts: image.PackingOption = { format: "image/jpeg", quality: IMAGE_QUALITY };

  // 通过PixelMap进行编码。compressedImageData为打包获取到的图片文件流。

  let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);

  // 压缩目标图像字节长度

  const maxCompressedImageByte = maxCompressedImageSize * 1024;

  // 图片压缩。先判断设置图片质量参数quality为0时,packing能压缩到的图片最小字节大小是否满足指定的图片压缩大小。如果满足,则使用packing方式二分查找最接近指定图片压缩目标大小的quality来压缩图片。如果不满足,则使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据。

  if (maxCompressedImageByte > compressedImageData.byteLength) {

    // 使用packing二分压缩获取图片文件流

    compressedImageData =

      await packingImage(compressedImageData, sourcePixelMap, IMAGE_QUALITY, maxCompressedImageByte);

  } else {

    // 使用scale对图片先进行缩放,采用while循环每次递减0.4倍缩放图片,再用packing(图片质量参数quality设置0)获取压缩图片大小,最终查找到最接近指定图片压缩目标大小的缩放倍数的图片压缩数据

    let imageScale = 1;

    const REDUCE_SCALE = 0.4;

    // 判断压缩后的图片大小是否大于指定图片的压缩目标大小,如果大于,继续降低缩放倍数压缩。

    while (compressedImageData.byteLength > maxCompressedImageByte) {

      if (imageScale > 0) {

        // 性能知识点: 由于scale会直接修改图片PixelMap数据,所以不适用二分查找scale缩放倍数。这里采用循环递减0.4倍缩放图片,来查找确定最适合的缩放倍数。如果对图片压缩质量要求不高,建议调高每次递减的缩放倍数reduceScale,减少循环,提升scale压缩性能。

        imageScale = imageScale - REDUCE_SCALE;

        await sourcePixelMap.scale(imageScale, imageScale);

        compressedImageData = await packing(sourcePixelMap, IMAGE_QUALITY);

      } else {

        // imageScale缩放小于等于0时,没有意义,结束压缩。这里不考虑图片缩放倍数小于reduceScale的情况。

        break;

      }

}

}

  // 保存图片,返回压缩后的图片信息。

  const compressedImageInfo: CompressedImageInfo = await saveImage(compressedImageData, compress);

  console.info('compressedImageInfo: ' + JSON.stringify(compressedImageInfo))

  return compressedImageInfo;

}

/**

* packing压缩 * @param sourcePixelMap:原始待压缩图片的PixelMap

* @param imageQuality:图片质量参数 * @returns data:返回压缩后的图片数据*/

async function 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;

}

/**

* packing二分方式循环压缩 * @param compressedImageData:图片压缩的ArrayBuffer

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

* @param imageQuality:图片质量参数 * @param maxCompressedImageByte:压缩目标图像字节长度 * @returns compressedImageData:返回二分packing压缩后的图片数据*/

async function packingImage(compressedImageData: ArrayBuffer, sourcePixelMap: image.PixelMap, imageQuality: number,

  maxCompressedImageByte: number): Promise<ArrayBuffer> {

  // 图片质量参数范围为0-100,这里以10为最小二分单位创建用于packing二分图片质量参数的数组。

  const packingArray: number[] = [];

  const DICHOTOMY_ACCURACY = 10;

  // 性能知识点: 如果对图片压缩质量要求不高,建议调高最小二分单位dichotomyAccuracy,减少循环,提升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 packing(sourcePixelMap, imageQuality);

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

    if (compressedImageData.byteLength <= maxCompressedImageByte) {

      left = mid + 1;

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

        break;

      }

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

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

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

      if (compressedImageData.byteLength > maxCompressedImageByte) {

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

        break;

      }

    } else {

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

      right = mid - 1;

    }

}

  return compressedImageData;

}

/**

* 图片保存 * @param compressedImageData:压缩后的图片数据 * @returns compressedImageInfo:返回压缩后的图片信息*/

async function saveImage(compressedImageData: ArrayBuffer, compress: CompressedImageInfo): Promise<CompressedImageInfo> {

  const context: Context = getContext();

  // 定义要保存的压缩图片uri。afterCompressiona.jpeg表示压缩后的图片。

  // const afterCompression = util.generateRandomUUID() + ".jpg"

// const compressedImageUri: string = context.filesDir + '/' + afterCompression;

  const compressedImageUri: string = compress.imageUri

  try {

    const res = fileIo.accessSync(compressedImageUri);

    if (res) {

      // 如果图片afterCompressiona.jpeg已存在,则删除

      fileIo.unlinkSync(compressedImageUri);

    }

  } catch (err) {

    console.error(`AccessSync failed with error message: ${err.message}, error code: ${err.code}`);

  }

  // 知识点:保存图片。获取最终图片压缩数据compressedImageData,保存图片。

  // 压缩图片数据写入文件

  const file: fileIo.File = fileIo.openSync(compressedImageUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);

  fileIo.writeSync(file.fd, compressedImageData);

  fileIo.closeSync(file);

  // 获取压缩图片信息

  let compressedImageInfo: CompressedImageInfo = new CompressedImageInfo();

  compressedImageInfo.imageUri = compressedImageUri;

  compressedImageInfo.name = compress.name;

  compressedImageInfo.imageByteLength = compressedImageData.byteLength;

  return compressedImageInfo;

}

//压缩图片使用

ysImage //返回的压缩图片class

参数1: 选取的图片uri

参数2:需压缩的实际图片大小

let ysImage = await compressedImage(element, 200)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351