鸿蒙学习实战之路:图片压缩与格式转换:优化应用资源加载

图片压缩与格式转换:优化应用资源加载

引言

在 HarmonyOS 应用开发中,图片资源的管理对应用性能至关重要。不合理的图片处理会导致应用体积膨胀、加载速度变慢,甚至引发内存溢出问题。本文将深入讲解如何在 HarmonyOS Next(API 10+)中进行高效的图片压缩和格式转换,帮助开发者优化应用资源加载体验。

官方参考资料:

图片处理基础概念

图片格式选择策略

在 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
  2. 合理设置图片质量

    • 列表图片: 60-70%
    • 详情页图片: 75-85%
    • 高清展示图片: 85-95%
  3. 预加载和懒加载结合

    • 预加载可见区域附近的图片
    • 懒加载远离可视区域的图片
  4. 避免重复解码

    • 使用缓存机制减少重复解码
    • 相同图片只解码一次
  5. 资源释放时机

    • 页面离开时清理缓存
    • 组件销毁时释放相关图片资源

代码优化建议

// 图片资源管理器 - 全局单例
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;
    }

需要参加鸿蒙认证的请点击 鸿蒙认证链接

posted @ 2025-11-30 23:36  时间煮鱼  阅读(17)  评论(0)    收藏  举报