圖片壓縮與格式轉換:優化應用資源加載
引言
在 HarmonyOS 應用開發中,圖片資源的管理對應用性能至關重要。不合理的圖片處理會導致應用體積膨脹、加載速度變慢,甚至引發內存溢出問題。本文將深入講解如何在 HarmonyOS Next(API 10+)中進行高效的圖片壓縮和格式轉換,幫助開發者優化應用資源加載體驗。
官方參考資料:
- HarmonyOS API 參考
- 媒體服務開發指南
圖片處理基礎概念
圖片格式選擇策略
在 HarmonyOS 應用開發中,選擇合適的圖片格式直接影響應用性能:
- JPEG 格式:適用於照片類圖片,支持高壓縮比
- PNG 格式:適用於需要透明度的圖標和圖形
- WebP 格式:現代格式,在相同質量下體積更小
- HEIC 格式:高效圖像格式,iOS 生態常用
圖片壓縮級別
// 壓縮質量級別定義示例
const CompressionLevel = {
LOW: 0.3, // 低質量,高壓縮
MEDIUM: 0.6, // 中等質量
HIGH: 0.8, // 高質量,低壓縮
LOSSLESS: 1.0, // 無損壓縮
} as const;
HarmonyOS 圖片處理 API 概覽
核心圖像處理類
HarmonyOS Next 提供了豐富的圖像處理 API:
- ImageSource:圖像數據源管理
- ImagePacker:圖像打包和壓縮
- PixelMap:像素級圖像操作
- ImageReceiver:圖像接收和處理
支持的圖像格式
|
格式類型 |
編碼支持 |
解碼支持 |
特性説明 |
|
JPEG |
✅ |
✅ |
有損壓縮,適合照片 |
|
PNG |
✅ |
✅ |
無損壓縮,支持透明 |
|
WebP |
✅ |
✅ |
現代格式,壓縮率高 |
|
HEIC |
✅ |
✅ |
高效圖像格式 |
|
GIF |
❌ |
✅ |
僅支持解碼 |
|
BMP |
❌ |
✅ |
僅支持解碼 |
圖片壓縮實戰
基礎壓縮方法
import image from '@ohos.multimedia.image';
import fileIo from '@ohos.file.fs';
// 基礎圖片壓縮函數
async function compressImage(sourceUri: string, targetUri: string, quality: number): Promise<boolean> {
try {
// 1. 創建ImageSource實例
const sourceFile = await fileIo.open(sourceUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(sourceFile.fd);
// 2. 創建解碼選項
const decodingOptions: image.DecodingOptions = {
desiredSize: { width: 1024, height: 1024 }, // 限制最大尺寸
desiredRegion: { size: { width: 1024, height: 1024 }, x: 0, y: 0 },
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
} catch (error) {
console.error('內存優化處理失敗:', error);
return false;
} finally {
// 確保資源釋放
if (originalPixelMap) {
originalPixelMap.release();
}
if (processedPixelMap && processedPixelMap !== originalPixelMap) {
processedPixelMap.release();
}
if (sourceFile) {
fileIo.close(sourceFile).catch(e => console.error('關閉文件失敗:', e));
}
}
}
}
## 圖片緩存策略
### 內存緩存實現
```typescript
// 內存緩存管理器
class ImageMemoryCache {
private cache: Map<string, { pixelMap: image.PixelMap, timestamp: number }>;
private maxSize: number;
private memoryUsage: number;
constructor(maxSizeBytes: number = 20 * 1024 * 1024) { // 默認20MB
this.cache = new Map();
this.maxSize = maxSizeBytes;
this.memoryUsage = 0;
}
async put(key: string, pixelMap: image.PixelMap): Promise<void> {
// 估算內存使用
const imageInfo = await pixelMap.getImageInfo();
const pixelFormat = await pixelMap.getPixelFormat();
const bytesPerPixel = this.getBytesPerPixel(pixelFormat);
const estimatedSize = imageInfo.size.width * imageInfo.size.height * bytesPerPixel;
// 檢查是否需要清理緩存
while (this.memoryUsage + estimatedSize > this.maxSize && this.cache.size > 0) {
this.evictOldest();
}
// 存儲到緩存
this.cache.set(key, { pixelMap, timestamp: Date.now() });
this.memoryUsage += estimatedSize;
}
get(key: string): image.PixelMap | null {
const cached = this.cache.get(key);
if (cached) {
// 更新訪問時間
cached.timestamp = Date.now();
return cached.pixelMap;
}
return null;
}
private evictOldest(): void {
let oldestKey = '';
let oldestTime = Infinity;
this.cache.forEach((value, key) => {
if (value.timestamp < oldestTime) {
oldestTime = value.timestamp;
oldestKey = key;
}
});
if (oldestKey) {
const removed = this.cache.get(oldestKey);
if (removed) {
removed.pixelMap.release();
}
this.cache.delete(oldestKey);
// 注意:這裏簡化了內存計算,實際應該記錄每個緩存項的大小
}
}
private getBytesPerPixel(format: number): number {
// 根據不同像素格式返回每像素字節數
switch (format) {
case image.PixelMapFormat.ARGB_8888:
case image.PixelMapFormat.RGBA_8888:
return 4;
case image.PixelMapFormat.RGB_565:
return 2;
default:
return 4; // 默認保守估計
}
}
}
// 使用示例
const imageCache = new ImageMemoryCache();
async function loadImageWithCache(imageUri: string): Promise<image.PixelMap | null> {
// 嘗試從緩存獲取
const cachedImage = imageCache.get(imageUri);
if (cachedImage) {
console.log('從緩存加載圖片:', imageUri);
return cachedImage;
}
// 緩存未命中,加載並緩存
try {
const file = await fileIo.open(imageUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
const pixelMap = await imageSource.createPixelMap();
await fileIo.close(file);
// 存入緩存
await imageCache.put(imageUri, pixelMap);
console.log('加載並緩存圖片:', imageUri);
return pixelMap;
} catch (error) {
console.error('加載圖片失敗:', error);
return null;
}
}
磁盤緩存實現
// 磁盤緩存管理器
class ImageDiskCache {
private cacheDir: string;
private maxSize: number;
constructor(
cacheDirectory: string,
maxSizeBytes: number = 100 * 1024 * 1024
) {
// 默認100MB
this.cacheDir = cacheDirectory;
this.maxSize = maxSizeBytes;
// 確保緩存目錄存在
this.ensureCacheDirExists();
}
private async ensureCacheDirExists(): Promise<void> {
try {
const fileStat = await fileIo.stat(this.cacheDir);
if (!fileStat.isDirectory) {
await fileIo.mkdir(this.cacheDir, { recursive: true });
}
} catch (error) {
// 目錄可能不存在,創建它
await fileIo.mkdir(this.cacheDir, { recursive: true });
}
}
async put(key: string, imageData: Uint8Array): Promise<void> {
const cachePath = this.getKeyPath(key);
try {
// 寫入文件
const file = await fileIo.open(
cachePath,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE
);
await fileIo.write(file.fd, imageData);
await fileIo.close(file);
// 更新最後修改時間
await fileIo.utimes(cachePath, Date.now() / 1000, Date.now() / 1000);
// 檢查緩存大小並清理
await this.cleanupIfNeeded();
} catch (error) {
console.error("寫入緩存失敗:", error);
}
}
async get(key: string): Promise<Uint8Array | null> {
const cachePath = this.getKeyPath(key);
try {
// 檢查文件是否存在
await fileIo.access(cachePath);
// 讀取文件內容
const file = await fileIo.open(cachePath, fileIo.OpenMode.READ_ONLY);
const fileStat = await fileIo.stat(cachePath);
const buffer = new ArrayBuffer(fileStat.size);
await fileIo.read(file.fd, buffer);
await fileIo.close(file);
// 更新最後訪問時間
await fileIo.utimes(cachePath, Date.now() / 1000, Date.now() / 1000);
return new Uint8Array(buffer);
} catch (error) {
// 文件不存在或讀取失敗
return null;
}
}
private getKeyPath(key: string): string {
// 使用簡單的哈希方式生成文件名
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash = (hash << 5) - hash + key.charCodeAt(i);
hash |= 0; // 轉換為32位整數
}
return `${this.cacheDir}/img_cache_${hash}.bin`;
}
private async cleanupIfNeeded(): Promise<void> {
try {
// 獲取緩存目錄中的所有文件
const files = await fileIo.readdir(this.cacheDir);
// 計算總大小
let totalSize = 0;
const fileStats: Array<{ path: string; size: number; mtime: number }> =
[];
for (const file of files) {
const filePath = `${this.cacheDir}/${file}`;
const stat = await fileIo.stat(filePath);
if (!stat.isDirectory) {
totalSize += stat.size;
fileStats.push({
path: filePath,
size: stat.size,
mtime: stat.mtime * 1000, // 轉換為毫秒
});
}
}
// 如果超過最大大小,刪除最舊的文件
if (totalSize > this.maxSize) {
// 按修改時間排序
fileStats.sort((a, b) => a.mtime - b.mtime);
while (totalSize > this.maxSize && fileStats.length > 0) {
const oldest = fileStats.shift();
if (oldest) {
await fileIo.unlink(oldest.path);
totalSize -= oldest.size;
}
}
}
} catch (error) {
console.error("清理緩存失敗:", error);
}
}
}
實際應用案例
列表圖片優化加載
// 圖片列表優化管理器
class OptimizedImageListManager {
private diskCache: ImageDiskCache;
private memoryCache: ImageMemoryCache;
private pendingOperations: Map<string, Promise<image.PixelMap | null>>;
constructor() {
this.diskCache = new ImageDiskCache("internal://app/image_cache");
this.memoryCache = new ImageMemoryCache();
this.pendingOperations = new Map();
}
async loadImageForList(
imageUri: string,
targetSize: { width: number; height: number }
): Promise<image.PixelMap | null> {
// 生成緩存鍵,包含目標尺寸
const cacheKey = `${imageUri}_${targetSize.width}x${targetSize.height}`;
// 檢查是否有相同的請求正在進行
if (this.pendingOperations.has(cacheKey)) {
return this.pendingOperations.get(cacheKey);
}
// 創建加載操作
const loadOperation = this.doLoadImage(cacheKey, imageUri, targetSize);
this.pendingOperations.set(cacheKey, loadOperation);
try {
return await loadOperation;
} finally {
// 移除已完成的操作
this.pendingOperations.delete(cacheKey);
}
}
private async doLoadImage(
cacheKey: string,
originalUri: string,
targetSize: { width: number; height: number }
): Promise<image.PixelMap | null> {
// 1. 嘗試從內存緩存獲取
const memoryCached = this.memoryCache.get(cacheKey);
if (memoryCached) {
return memoryCached;
}
// 2. 嘗試從磁盤緩存獲取
const diskCached = await this.diskCache.get(cacheKey);
if (diskCached) {
// 從緩存數據創建PixelMap
const pixelMap = await this.createPixelMapFromData(diskCached);
if (pixelMap) {
await this.memoryCache.put(cacheKey, pixelMap);
}
return pixelMap;
}
// 3. 加載原圖並處理
try {
const file = await fileIo.open(originalUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(file.fd);
// 解碼選項,按目標尺寸縮放
const decodingOptions: image.DecodingOptions = {
desiredSize: targetSize,
desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
};
// 創建縮放後的PixelMap
const pixelMap = await imageSource.createPixelMap(decodingOptions);
await fileIo.close(file);
// 編碼處理後的圖片用於緩存
const imagePacker = image.createImagePacker();
const packingOptions: image.PackingOption = {
format: "image/webp",
quality: 80,
};
const packedData = await imagePacker.packing(pixelMap, packingOptions);
// 保存到緩存
await this.diskCache.put(cacheKey, packedData);
await this.memoryCache.put(cacheKey, pixelMap);
return pixelMap;
} catch (error) {
console.error("加載並處理圖片失敗:", error);
return null;
}
}
private async createPixelMapFromData(
data: Uint8Array
): Promise<image.PixelMap | null> {
try {
// 創建內存緩衝區
const buffer = new ArrayBuffer(data.length);
const view = new Uint8Array(buffer);
for (let i = 0; i < data.length; i++) {
view[i] = data[i];
}
// 從緩衝區創建ImageSource
const imageSource = image.createImageSource(buffer);
return await imageSource.createPixelMap();
} catch (error) {
console.error("從緩存數據創建PixelMap失敗:", error);
return null;
}
}
}
性能優化最佳實踐
圖片資源優化建議
- 選擇合適的圖片格式
- 照片類圖片使用 JPEG 格式
- 圖標和需要透明度的圖片使用 PNG 或 WebP
- 追求最佳壓縮率時使用 WebP
- 合理設置圖片質量
- 列表圖片: 60-70%
- 詳情頁圖片: 75-85%
- 高清展示圖片: 85-95%
- 預加載和懶加載結合
- 預加載可見區域附近的圖片
- 懶加載遠離可視區域的圖片
- 避免重複解碼
- 使用緩存機制減少重複解碼
- 相同圖片只解碼一次
- 資源釋放時機
- 頁面離開時清理緩存
- 組件銷燬時釋放相關圖片資源
代碼優化建議
// 圖片資源管理器 - 全局單例
class ImageResourceManager {
private static instance: ImageResourceManager;
private memoryCache: ImageMemoryCache;
private diskCache: ImageDiskCache;
private activeResources: Set<string>;
private constructor() {
this.memoryCache = new ImageMemoryCache();
this.diskCache = new ImageDiskCache("internal://app/image_cache");
this.activeResources = new Set();
}
public static getInstance(): ImageResourceManager {
if (!ImageResourceManager.instance) {
ImageResourceManager.instance = new ImageResourceManager();
}
return ImageResourceManager.instance;
}
// 註冊使用中的資源
public registerResource(resourceId: string): void {
this.activeResources.add(resourceId);
}
// 註銷不再使用的資源
public unregisterResource(resourceId: string): void {
this.activeResources.delete(resourceId);
// 可以在這裏添加資源清理邏輯
}
// 清理未使用的資源
public async cleanupUnused(): Promise<void> {
// 實現資源清理邏輯
console.log("清理未使用的圖片資源");
}
// 應用退出時釋放所有資源
public async releaseAll(): Promise<void> {
// 釋放所有緩存資源
console.log("釋放所有圖片資源");
}
}
結語
本文詳細介紹了在 HarmonyOS 應用開發中進行圖片壓縮與格式轉換的技術要點。通過合理運用這些技術,可以顯著提升應用的性能表現,減少資源消耗,改善用户體驗。
在實際開發過程中,建議結合應用的具體場景和需求,選擇合適的圖片處理策略。同時,也要注意在追求性能優化的同時,保證圖片的顯示質量,找到性能與質量之間的平衡點。
隨着 HarmonyOS 的不斷髮展,相信未來會有更多高效的圖片處理 API 和技術出現,讓開發者能夠更輕鬆地優化應用資源加載。;
// 3. 解碼圖片
const pixelMap = await imageSource.createPixelMap(decodingOptions);
// 4. 創建打包選項
const packingOptions: image.PackingOption = {
format: "image/jpeg",
quality: quality // 壓縮質量 0-100
};
// 5. 創建ImagePacker並打包
const imagePacker = image.createImagePacker();
const packResult = await imagePacker.packing(pixelMap, packingOptions);
// 6. 保存壓縮後的圖片
const targetFile = await fileIo.open(targetUri, fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE);
await fileIo.write(targetFile.fd, packResult);
// 7. 釋放資源
await fileIo.close(sourceFile);
await fileIo.close(targetFile);
pixelMap.release();
return true;
} catch (error) { console.error('圖片壓縮失敗:', error); return false; } }
### 智能尺寸壓縮
```typescript
// 根據目標尺寸智能壓縮
async function smartCompressBySize(
sourceUri: string,
targetUri: string,
maxWidth: number,
maxHeight: number
): Promise<boolean> {
try {
const sourceFile = await fileIo.open(sourceUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(sourceFile.fd);
// 獲取圖片原始尺寸
const imageInfo = await imageSource.getImageInfo();
const originalWidth = imageInfo.size.width;
const originalHeight = imageInfo.size.height;
// 計算縮放比例
const scale = Math.min(maxWidth / originalWidth, maxHeight / originalHeight, 1);
const targetWidth = Math.round(originalWidth * scale);
const targetHeight = Math.round(originalHeight * scale);
const decodingOptions: image.DecodingOptions = {
desiredSize: { width: targetWidth, height: targetHeight },
desiredPixelFormat: image.PixelMapFormat.RGBA_8888
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
const imagePacker = image.createImagePacker();
const packingOptions: image.PackingOption = {
format: "image/jpeg",
quality: 85 // 保持較好質量的壓縮
};
const packResult = await imagePacker.packing(pixelMap, packingOptions);
const targetFile = await fileIo.open(targetUri, fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE);
await fileIo.write(targetFile.fd, packResult);
await fileIo.close(sourceFile);
await fileIo.close(targetFile);
pixelMap.release();
console.log(`圖片從 ${originalWidth}x${originalHeight} 壓縮到 ${targetWidth}x${targetHeight}`);
return true;
} catch (error) {
console.error('智能壓縮失敗:', error);
return false;
}
}
批量圖片壓縮
// 批量處理多張圖片
class BatchImageCompressor {
private maxConcurrent: number;
constructor(maxConcurrent: number = 3) {
this.maxConcurrent = maxConcurrent;
}
async compressImages(
imageList: Array<{ source: string; target: string }>,
quality: number
): Promise<Array<{ source: string; target: string; success: boolean }>> {
const results: Array<{ source: string; target: string; success: boolean }> =
[];
// 控制併發數量
for (let i = 0; i < imageList.length; i += this.maxConcurrent) {
const batch = imageList.slice(i, i + this.maxConcurrent);
const batchPromises = batch.map(async (item) => {
const success = await compressImage(item.source, item.target, quality);
return { ...item, success };
});
const batchResults = await Promise.all(batchPromises);
results.push(...batchResults);
}
return results;
}
}
// 使用示例
const compressor = new BatchImageCompressor(2);
const imagesToCompress = [
{
source: "internal://app/images/photo1.jpg",
target: "internal://app/compressed/photo1.jpg",
},
{
source: "internal://app/images/photo2.png",
target: "internal://app/compressed/photo2.jpg",
},
{
source: "internal://app/images/photo3.webp",
target: "internal://app/compressed/photo3.jpg",
},
];
compressor.compressImages(imagesToCompress, 75).then((results) => {
results.forEach((result) => {
console.log(
`圖片 ${result.source} 壓縮${result.success ? "成功" : "失敗"}`
);
});
});
圖片格式轉換
基礎格式轉換
// 通用格式轉換函數
async function convertImageFormat(
sourceUri: string,
targetUri: string,
targetFormat: string,
quality: number = 80
): Promise<boolean> {
try {
const sourceFile = await fileIo.open(sourceUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(sourceFile.fd);
// 解碼原圖
const pixelMap = await imageSource.createPixelMap();
// 格式轉換打包選項
const packingOptions: image.PackingOption = {
format: targetFormat,
quality: quality,
};
const imagePacker = image.createImagePacker();
const packResult = await imagePacker.packing(pixelMap, packingOptions);
// 保存轉換後的圖片
const targetFile = await fileIo.open(
targetUri,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE
);
await fileIo.write(targetFile.fd, packResult);
await fileIo.close(sourceFile);
await fileIo.close(targetFile);
pixelMap.release();
console.log(`格式轉換完成: ${sourceUri} -> ${targetUri} (${targetFormat})`);
return true;
} catch (error) {
console.error("格式轉換失敗:", error);
return false;
}
}
PNG 轉 WebP 優化
// PNG轉WebP專項優化
async function pngToWebPOptimized(
sourceUri: string,
targetUri: string,
quality: number = 75
): Promise<{ success: boolean; originalSize: number; compressedSize: number }> {
try {
// 獲取原文件大小
const fileStats = await fileIo.stat(sourceUri);
const originalSize = fileStats.size;
const sourceFile = await fileIo.open(sourceUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(sourceFile.fd);
// 對於PNG轉WebP,可以優化解碼選項
const decodingOptions: image.DecodingOptions = {
desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
};
const pixelMap = await imageSource.createPixelMap(decodingOptions);
// WebP特定打包選項
const packingOptions: image.PackingOption = {
format: "image/webp",
quality: quality,
};
const imagePacker = image.createImagePacker();
const packResult = await imagePacker.packing(pixelMap, packingOptions);
const targetFile = await fileIo.open(
targetUri,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE
);
await fileIo.write(targetFile.fd, packResult);
// 獲取壓縮後文件大小
const compressedStats = await fileIo.stat(targetUri);
const compressedSize = compressedStats.size;
const compressionRatio = (
((originalSize - compressedSize) / originalSize) *
100
).toFixed(2);
console.log(
`壓縮率: ${compressionRatio}% (${originalSize} -> ${compressedSize} bytes)`
);
await fileIo.close(sourceFile);
await fileIo.close(targetFile);
pixelMap.release();
return {
success: true,
originalSize,
compressedSize,
};
} catch (error) {
console.error("PNG轉WebP失敗:", error);
return { success: false, originalSize: 0, compressedSize: 0 };
}
}
支持的目標格式配置
// 支持的轉換格式配置
const SupportedConversions = {
JPEG: {
format: "image/jpeg",
extensions: [".jpg", ".jpeg"],
maxQuality: 100,
supportsLossless: false,
},
PNG: {
format: "image/png",
extensions: [".png"],
maxQuality: 100,
supportsLossless: true,
},
WEBP: {
format: "image/webp",
extensions: [".webp"],
maxQuality: 100,
supportsLossless: true,
},
} as const;
// 格式轉換管理器
class FormatConversionManager {
async convertWithOptions(
sourceUri: string,
targetUri: string,
options: {
targetFormat: keyof typeof SupportedConversions;
quality?: number;
maxWidth?: number;
maxHeight?: number;
}
): Promise<boolean> {
const formatConfig = SupportedConversions[options.targetFormat];
const effectiveQuality = Math.min(
options.quality || 80,
formatConfig.maxQuality
);
try {
const sourceFile = await fileIo.open(
sourceUri,
fileIo.OpenMode.READ_ONLY
);
const imageSource = image.createImageSource(sourceFile.fd);
// 動態設置解碼選項
const decodingOptions: image.DecodingOptions = {
desiredPixelFormat: image.PixelMapFormat.RGBA_8888,
};
if (options.maxWidth && options.maxHeight) {
decodingOptions.desiredSize = {
width: options.maxWidth,
height: options.maxHeight,
};
}
const pixelMap = await imageSource.createPixelMap(decodingOptions);
const imagePacker = image.createImagePacker();
const packingOptions: image.PackingOption = {
format: formatConfig.format,
quality: effectiveQuality,
};
const packResult = await imagePacker.packing(pixelMap, packingOptions);
const targetFile = await fileIo.open(
targetUri,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE
);
await fileIo.write(targetFile.fd, packResult);
await fileIo.close(sourceFile);
await fileIo.close(targetFile);
pixelMap.release();
return true;
} catch (error) {
console.error(`格式轉換失敗 [${options.targetFormat}]:`, error);
return false;
}
}
}
高級優化技巧
漸進式加載優化
// 漸進式JPEG生成
async function createProgressiveJPEG(
sourceUri: string,
targetUri: string,
quality: number = 75
): Promise<boolean> {
try {
const sourceFile = await fileIo.open(sourceUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(sourceFile.fd);
const pixelMap = await imageSource.createPixelMap();
const imagePacker = image.createImagePacker();
// 漸進式JPEG配置
const packingOptions: image.PackingOption = {
format: "image/jpeg",
quality: quality,
// 注意:HarmonyOS API 10中漸進式JPEG支持需要檢查具體實現
};
const packResult = await imagePacker.packing(pixelMap, packingOptions);
const targetFile = await fileIo.open(
targetUri,
fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE
);
await fileIo.write(targetFile.fd, packResult);
await fileIo.close(sourceFile);
await fileIo.close(targetFile);
pixelMap.release();
return true;
} catch (error) {
console.error("生成漸進式JPEG失敗:", error);
return false;
}
}
內存優化處理
// 內存優化的圖片處理
class MemoryOptimizedImageProcessor {
private readonly MAX_MEMORY_USAGE = 50 * 1024 * 1024; // 50MB
async processWithMemoryLimit(
sourceUri: string,
processingCallback: (pixelMap: image.PixelMap) => Promise<image.PixelMap>
): Promise<boolean> {
let sourceFile: fileIo.File | null = null;
let originalPixelMap: image.PixelMap | null = null;
let processedPixelMap: image.PixelMap | null = null;
try {
sourceFile = await fileIo.open(sourceUri, fileIo.OpenMode.READ_ONLY);
const imageSource = image.createImageSource(sourceFile.fd);
// 獲取圖片信息評估內存使用
const imageInfo = await imageSource.getImageInfo();
const estimatedMemory = imageInfo.size.width * imageInfo.size.height * 4; // RGBA
if (estimatedMemory > this.MAX_MEMORY_USAGE) {
// 大圖片需要先縮放
const scale = Math.sqrt(this.MAX_MEMORY_USAGE / estimatedMemory);
const decodingOptions: image.DecodingOptions = {
desiredSize: {
width: Math.round(imageInfo.size.width * scale),
height: Math.round(imageInfo.size.height * scale)
}
};
originalPixelMap = await imageSource.createPixelMap(decodingOptions);
} else {
originalPixelMap = await imageSource.createPixelMap();
}
// 執行處理回調
processedPixelMap = await processingCallback(originalPixelMap);
return true;
}
需要參加鴻蒙認證的請點擊 鴻蒙認證鏈接