HarmonyOS NEXT應(yīng)用開發(fā)—圖片壓縮方案

介紹

圖片壓縮在應(yīng)用開發(fā)中是一個非常常見的需求,特別是在處理用戶上傳圖片時,需要上傳指定大小以內(nèi)的圖片。目前圖片壓縮支持jpeg、webp、png格式。本例中以jpeg圖片為例介紹如何通過packing和scale實現(xiàn)圖片壓縮到目標(biāo)大小以內(nèi)。

效果圖預(yù)覽

使用說明

  1. 進(jìn)入頁面,輸入圖片壓縮目標(biāo)大小,點擊“圖片壓縮”按鈕查看壓縮后的圖片。效果圖中輸入圖片壓縮目標(biāo)大小為5kb,實際壓縮大小小于等于5kb,不一定準(zhǔn)確為5kb。

實現(xiàn)思路

  1. 獲取圖片。從資源管理器獲取要壓縮的圖片,創(chuàng)建ImageSource實例,設(shè)置解碼參數(shù)DecodingOptions,使用createPixelMap獲取PixelMap圖片對象。源碼參考ImageCompression.ets
// 獲取resourceManager資源管理器
const resourceMgr: resourceManager.ResourceManager = this.context.resourceManager;
// 獲取資源管理器后,再調(diào)用resourceMgr.getRawFileContent()獲取資源文件的ArrayBuffer。
resourceMgr.getRawFileContent('beforeCompression.jpeg').then((fileData: Uint8Array) => {
  // 獲取圖片的ArrayBuffer
  const buffer = fileData.buffer.slice(0);
  // 創(chuàng)建ImageSource實例
  const imageSource: image.ImageSource = image.createImageSource(buffer);
  // 設(shè)置解碼參數(shù)DecodingOptions,解碼獲取PixelMap圖片對象。
  let decodingOptions: image.DecodingOptions = {
    editable: true, // 是否可編輯。當(dāng)取值為false時,圖片不可二次編輯,如crop等操作將失敗。
    desiredPixelFormat: 3, // 解碼的像素格式。3表示RGBA_8888。
  }
  // 創(chuàng)建pixelMap
  imageSource.createPixelMap(decodingOptions).then((originalPixelMap: image.PixelMap) => {
    // 壓縮圖片
    compressedImage(originalPixelMap, this.maxCompressedImageSize).then((showImage: CompressedImageInfo) => {
      // 獲取壓縮后的圖片信息
      this.compressedImageSrc = fileUri.getUriFromPath(showImage.imageUri);
      this.compressedByteLength = showImage.imageByteLength;
      this.afterCompressionSize = (this.compressedByteLength / BYTE_CONVERSION).toFixed(1);
    })
  }).catch((err: BusinessError) => {
    logger.error(TAG, `Failed to create PixelMap with error message: ${err.message}, error code: ${err.code}`);
  });
}).catch((err: BusinessError) => {
  logger.error(TAG, `Failed to get RawFileContent with error message: ${err.message}, error code: ${err.code}`);
});
  1. 圖片壓縮。先判斷設(shè)置圖片質(zhì)量參數(shù)quality為0時,packing能壓縮到的圖片最小字節(jié)大小是否滿足指定的圖片壓縮大小。如果滿足,則使用packing方式二分查找最接近指定圖片壓縮目標(biāo)大小的quality來壓縮圖片。如果不滿足,則使用scale對圖片先進(jìn)行縮放,采用while循環(huán)每次遞減0.4倍縮放圖片,再用packing(圖片質(zhì)量參數(shù)quality設(shè)置0)獲取壓縮圖片大小,最終查找到最接近指定圖片壓縮目標(biāo)大小的縮放倍數(shù)的圖片壓縮數(shù)據(jù)。源碼參考ImageCompression.ets
// 創(chuàng)建圖像編碼ImagePacker對象
let imagePackerApi = image.createImagePacker();
// 定義圖片質(zhì)量參數(shù)
let imageQuality = 0;
// 設(shè)置編碼輸出流和編碼參數(shù)。圖片質(zhì)量參數(shù)quality范圍0-100。
let packOpts: image.PackingOption = { format: "image/jpeg", quality: imageQuality };
// 通過PixelMap進(jìn)行編碼。compressedImageData為打包獲取到的圖片文件流。
let compressedImageData: ArrayBuffer = await imagePackerApi.packing(sourcePixelMap, packOpts);
// 壓縮目標(biāo)圖像字節(jié)長度
let maxCompressedImageByte = maxCompressedImageSize * BYTE_CONVERSION;
if (maxCompressedImageByte > compressedImageData.byteLength) {
  // 使用packing二分壓縮獲取圖片文件流
  compressedImageData = await packingImage(compressedImageData, sourcePixelMap, imageQuality, maxCompressedImageByte);
} else {
  // 使用scale對圖片先進(jìn)行縮放,采用while循環(huán)每次遞減0.4倍縮放圖片,再用packing(圖片質(zhì)量參數(shù)quality設(shè)置0)獲取壓縮圖片大小,最終查找到最接近指定圖片壓縮目標(biāo)大小的縮放倍數(shù)的圖片壓縮數(shù)據(jù)。
  let imageScale = 1; // 定義圖片寬高的縮放倍數(shù),1表示原比例。
  let reduceScale = 0.4; // 圖片縮小倍數(shù)
  // 判斷壓縮后的圖片大小是否大于指定圖片的壓縮目標(biāo)大小,如果大于,繼續(xù)降低縮放倍數(shù)壓縮。
  while (compressedImageData.byteLength > maxCompressedImageByte) {
    if (imageScale > 0) {
      // 性能知識點: 由于scale會直接修改圖片PixelMap數(shù)據(jù),所以不適用二分查找scale縮放倍數(shù)。這里采用循環(huán)遞減0.4倍縮放圖片,來查找確定最適
      // 合的縮放倍數(shù)。如果對圖片壓縮質(zhì)量要求不高,建議調(diào)高每次遞減的縮放倍數(shù)reduceScale,減少循環(huán),提升scale壓縮性能。
      imageScale = imageScale - reduceScale; // 每次縮放倍數(shù)減0.4
      // 使用scale對圖片進(jìn)行縮放
      await sourcePixelMap.scale(imageScale, imageScale);
      // packing壓縮
      compressedImageData = await packing(sourcePixelMap, imageQuality);
    } else {
      // imageScale縮放小于等于0時,沒有意義,結(jié)束壓縮。這里不考慮圖片縮放倍數(shù)小于reduceScale的情況。
      break;
    }
  }
}
  1. 保存圖片。獲取最終圖片壓縮數(shù)據(jù)compressedImageData,保存圖片。源碼參考ImageCompression.ets
let context: Context = getContext();
// 定義要保存的壓縮圖片uri。afterCompressiona.jpeg表示壓縮后的圖片。
let compressedImageUri: string = context.filesDir + '/' + 'afterCompressiona.jpeg';
try {
  let res = fs.accessSync(compressedImageUri);
  if (res) {
    // 如果圖片afterCompressiona.jpeg已存在,則刪除
    fs.unlinkSync(compressedImageUri);
  }
} catch (err) {
  logger.error(TAG, `AccessSync failed with error message: ${err.message}, error code: ${err.code}`);
}
// 壓縮圖片數(shù)據(jù)寫入文件
let file: fs.File = fs.openSync(compressedImageUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(file.fd, compressedImageData);
fs.closeSync(file);
// 獲取壓縮圖片信息
let compressedImageInfo: CompressedImageInfo = new CompressedImageInfo();
compressedImageInfo.imageUri = compressedImageUri;
compressedImageInfo.imageByteLength = compressedImageData.byteLength;

高性能知識點

本示例packing方式壓縮圖片時,使用二分查找最接近指定圖片壓縮目標(biāo)大小的圖片質(zhì)量quality來壓縮圖片,提升查找性能。

工程結(jié)構(gòu)&模塊類型

imagecompression                               // har類型
|---view
|   |---ImageCompression.ets                   // 視圖層-圖片壓縮頁面

模塊依賴

本示例依賴common模塊來實現(xiàn)日志的打印、動態(tài)路由模塊來實現(xiàn)頁面的動態(tài)加載。

參考資料

  1. 圖片編碼
  2. packing
  3. scale
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容