介紹
圖片壓縮在應(yīng)用開發(fā)中是一個非常常見的需求,特別是在處理用戶上傳圖片時,需要上傳指定大小以內(nèi)的圖片。目前圖片壓縮支持jpeg、webp、png格式。本例中以jpeg圖片為例介紹如何通過packing和scale實現(xiàn)圖片壓縮到目標(biāo)大小以內(nèi)。
效果圖預(yù)覽
使用說明
- 進(jìn)入頁面,輸入圖片壓縮目標(biāo)大小,點擊“圖片壓縮”按鈕查看壓縮后的圖片。效果圖中輸入圖片壓縮目標(biāo)大小為5kb,實際壓縮大小小于等于5kb,不一定準(zhǔn)確為5kb。
實現(xiàn)思路
- 獲取圖片。從資源管理器獲取要壓縮的圖片,創(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}`);
});
- 圖片壓縮。先判斷設(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;
}
}
}
- 保存圖片。獲取最終圖片壓縮數(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)加載。