圖片壓縮與格式轉換:優化應用資源加載

引言

在 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;
    }
  }
}

性能優化最佳實踐

圖片資源優化建議

  1. 選擇合適的圖片格式
  • 照片類圖片使用 JPEG 格式
  • 圖標和需要透明度的圖片使用 PNG 或 WebP
  • 追求最佳壓縮率時使用 WebP
  1. 合理設置圖片質量
  • 列表圖片: 60-70%
  • 詳情頁圖片: 75-85%
  • 高清展示圖片: 85-95%
  1. 預加載和懶加載結合
  • 預加載可見區域附近的圖片
  • 懶加載遠離可視區域的圖片
  1. 避免重複解碼
  • 使用緩存機制減少重複解碼
  • 相同圖片只解碼一次
  1. 資源釋放時機
  • 頁面離開時清理緩存
  • 組件銷燬時釋放相關圖片資源

代碼優化建議

// 圖片資源管理器 - 全局單例
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;
    }


需要參加鴻蒙認證的請點擊 鴻蒙認證鏈接