HarmonyOS_ArkTs_API(6)

概述

图片处理系统是"梦想生活规划师"应用的重要组成部分,负责管理用户上传的图像资源。该系统基于鸿蒙HarmonyOS ArkTS构建,支持多种图片相关功能,包括图片上传、压缩、存储、裁剪和显示。通过高效且用户友好的图片处理能力,该系统为应用的内容丰富性提供了强大支持,使用户能够通过视觉元素更好地记录和分享他们的梦想历程。

核心功能

图片选择与拍照

系统提供了多种方式获取图片资源:

import { fileAccess, picker } from '@ohos.file.picker';
import { camera, CameraKit } from '@ohos.multimedia.camera';

// 从相册选择图片
export async function selectImagesFromGallery(maxCount: number = 9): Promise<string[]> {
  try {
    const photoPicker = new picker.PhotoViewPicker();
    const photoSelectOptions: picker.PhotoSelectOptions = {
      MIMEType: picker.PhotoViewMIMETypes.IMAGE_TYPE,
      maxSelectNumber: maxCount
    };
    
    const photoSelectResult = await photoPicker.select(photoSelectOptions);
    
    if (photoSelectResult && photoSelectResult.photoUris && photoSelectResult.photoUris.length > 0) {
      return photoSelectResult.photoUris;
    }
    return [];
  } catch (error) {
    console.error(`从相册选择图片失败: ${error instanceof Error ? error.message : String(error)}`);
    throw new Error(`从相册选择图片失败: ${error instanceof Error ? error.message : String(error)}`);
  }
}

// 使用相机拍摄图片
export async function takePhotoWithCamera(): Promise<string> {
  try {
    // 创建相机实例
    const cameraManager = await camera.getCameraManager(getContext());
    const cameraDevices = await cameraManager.getCameras();
    
    if (cameraDevices.length === 0) {
      throw new Error('没有找到可用相机设备');
    }
    
    const cameraDevice = await cameraManager.createCamera(cameraDevices[0].cameraId);
    
    // 创建并配置CameraKit
    const cameraKit = await CameraKit.getInstance(getContext());
    await cameraKit.createSession(cameraDevice);
    
    // 拍照并获取图片路径
    const photoPath = await new Promise<string>((resolve, reject) => {
      cameraKit.takePicture({
        quality: 85,
        rotation: 0
      }).then((result) => {
        resolve(result.fileUri);
      }).catch((err) => {
        reject(err);
      });
    });
    
    // 释放相机资源
    await cameraKit.release();
    
    return photoPath;
  } catch (error) {
    console.error(`使用相机拍照失败: ${error instanceof Error ? error.message : String(error)}`);
    throw new Error(`使用相机拍照失败: ${error instanceof Error ? error.message : String(error)}`);
  }
}

图片上传服务

处理图片上传到服务器的核心功能:

import { request, RequestMethod } from '../utils/HttpUtil';
import { UploadResponse } from '../model/CommonTypes';

// 图片上传API服务
export async function uploadImage(imagePath: string): Promise<string> {
  try {
    console.info(`开始上传图片: ${imagePath}`);
    
    // 创建FormData
    const formData = new FormData();
    formData.append('file', imagePath);
    
    // 设置请求头
    const headers = {
      'Content-Type': 'multipart/form-data'
    };
    
    // 发送上传请求
    const response = await request<UploadResponse>(
      RequestMethod.POST,
      '/upload/image',
      formData,
      undefined,
      headers
    );
    
    console.info(`图片上传成功: ${JSON.stringify(response)}`);
    
    return response.url; // 返回服务器存储的图片URL
  } catch (error) {
    console.error(`图片上传失败: ${error instanceof Error ? error.message : String(error)}`);
    throw new Error(`图片上传失败: ${error instanceof Error ? error.message : String(error)}`);
  }
}

// 上传多张图片
export async function uploadImages(imagePaths: string[]): Promise<string[]> {
  try {
    console.info(`开始上传${imagePaths.length}张图片`);
    
    const uploadPromises = imagePaths.map(path => uploadImage(path));
    const results = await Promise.all(uploadPromises);
    
    console.info(`所有图片上传完成,共${results.length}`);
    return results;
  } catch (error) {
    console.error(`多图上传失败: ${error instanceof Error ? error.message : String(error)}`);
    throw new Error(`多图上传失败: ${error instanceof Error ? error.message : String(error)}`);
  }
}

图片压缩与处理

在上传前对图片进行压缩和处理,以优化传输和存储:

import image from '@ohos.multimedia.image';

// 图片压缩
export async function compressImage(imagePath: string, quality: number = 80): Promise<string> {
  try {
    // 创建图像源
    const imageSourceApi = image.createImageSource(imagePath);
    if (!imageSourceApi) {
      throw new Error('无法创建图像源');
    }
    
    // 获取原图尺寸
    const imageInfo = await imageSourceApi.getImageInfo();
    const originalWidth = imageInfo.size.width;
    const originalHeight = imageInfo.size.height;
    
    // 计算压缩后的尺寸(保持纵横比)
    const maxDimension = 1200; // 最大尺寸
    let targetWidth = originalWidth;
    let targetHeight = originalHeight;
    
    if (originalWidth > maxDimension || originalHeight > maxDimension) {
      if (originalWidth > originalHeight) {
        targetWidth = maxDimension;
        targetHeight = Math.floor((originalHeight / originalWidth) * maxDimension);
      } else {
        targetHeight = maxDimension;
        targetWidth = Math.floor((originalWidth / originalHeight) * maxDimension);
      }
    }
    
    // 创建压缩选项
    const compressOptions = {
      size: { width: targetWidth, height: targetHeight },
      format: image.ImageFormat.JPEG,
      quality: quality
    };
    
    // 创建新图像包
    const imagePackerApi = image.createImagePacker();
    
    // 压缩图像
    const compressedData = await imagePackerApi.packing(imageSourceApi, compressOptions);
    
    // 保存压缩后的图像到临时文件
    const appContext = getContext();
    const tempDir = appContext.cacheDir;
    const tempFileName = `compressed_${Date.now()}.jpg`;
    const tempFilePath = `${tempDir}/${tempFileName}`;
    
    const fileIo = fileAccess.createFile(tempFilePath);
    await fileIo.write(compressedData);
    
    // 释放资源
    imagePackerApi.release();
    imageSourceApi.release();
    
    return tempFilePath;
  } catch (error) {
    console.error(`图片压缩失败: ${error instanceof Error ? error.message : String(error)}`);
    throw new Error(`图片压缩失败: ${error instanceof Error ? error.message : String(error)}`);
  }
}

// 处理多张图片(压缩)
export async function processBatchImages(imagePaths: string[]): Promise<string[]> {
  const processedPaths: string[] = [];
  
  for (const path of imagePaths) {
    try {
      const compressedPath = await compressImage(path);
      processedPaths.push(compressedPath);
    } catch (error) {
      console.error(`处理图片失败: ${path}, 错误: ${error instanceof Error ? error.message : String(error)}`);
      // 继续处理其他图片
    }
  }
  
  return processedPaths;
}

UI组件

图片上传组件

用于管理图片选择和上传过程的自定义组件:

@Component
export struct ImageUploaderComponent {
  @State private selectedImages: string[] = [];
  @State private uploading: boolean = false;
  @State private progress: number = 0;
  
  @Link onImagesSelected: (urls: string[]) => void;
  private maxImageCount: number = 9;
  
  build() {
    Column() {
      // 已选择图片预览
      if (this.selectedImages.length > 0) {
        Grid() {
          ForEach(this.selectedImages, (image: string, index: number) => {
            GridItem() {
              Stack() {
                Image(image)
                  .width('100%')
                  .height(100)
                  .objectFit(ImageFit.Cover)
                  .borderRadius(8)
                
                // 删除按钮
                Button({ type: ButtonType.Circle, stateEffect: true }) {
                  Image($r('app.media.ic_close'))
                    .width(16)
                    .height(16)
                }
                .width(24)
                .height(24)
                .position({ x: '100%', y: 0 })
                .translate({ x: -12, y: -12 })
                .backgroundColor('#FF4757')
                .onClick(() => {
                  this.removeImage(index);
                })
              }
              .width('100%')
              .height(100)
            }
            .width('30%')
            .height(100)
            .margin(4)
          })
          
          // 添加更多图片按钮(如果未达到上限)
          if (this.selectedImages.length < this.maxImageCount) {
            GridItem() {
              Button() {
                Image($r('app.media.ic_add_photo'))
                  .width(32)
                  .height(32)
              }
              .width('100%')
              .height(100)
              .backgroundColor('#F5F5F5')
              .borderRadius(8)
              .onClick(() => {
                this.showImagePickerDialog();
              })
            }
            .width('30%')
            .height(100)
            .margin(4)
          }
        }
        .columnsTemplate('1fr 1fr 1fr')
        .width('100%')
        .margin({ top: 16, bottom: 16 })
      } else {
        // 空状态 - 图片选择按钮
        Button() {
          Row() {
            Image($r('app.media.ic_photo'))
              .width(24)
              .height(24)
              .margin({ right: 8 })
            
            Text('添加图片')
              .fontSize(16)
          }
        }
        .height(56)
        .width('100%')
        .backgroundColor('#F5F5F5')
        .borderRadius(8)
        .margin({ top: 16, bottom: 16 })
        .onClick(() => {
          this.showImagePickerDialog();
        })
      }
      
      // 上传进度(如果正在上传)
      if (this.uploading) {
        Row() {
          Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
            .width('80%')
            .height(4)
            .color('#4E6EF2')
          
          Text(`${this.progress}%`)
            .fontSize(14)
            .fontColor('#666666')
            .margin({ left: 8 })
        }
        .width('100%')
        .margin({ top: 8 })
      }
    }
    .width('100%')
  }
  
  // 显示图片选择对话框
  private showImagePickerDialog(): void {
    ActionSheet.show({
      title: '选择图片来源',
      sheets: [
        { title: '从相册选择' },
        { title: '拍摄照片' }
      ],
      onSelect: (index: number) => {
        if (index === 0) {
          this.selectFromGallery();
        } else if (index === 1) {
          this.takePhoto();
        }
      }
    });
  }
  
  // 从相册选择图片
  private async selectFromGallery(): Promise<void> {
    try {
      const remainingCount = this.maxImageCount - this.selectedImages.length;
      const selected = await selectImagesFromGallery(remainingCount);
      
      if (selected.length > 0) {
        // 处理选择的图片(压缩)
        const processed = await processBatchImages(selected);
        this.selectedImages = [...this.selectedImages, ...processed];
        
        // 上传图片
        this.uploadSelectedImages();
      }
    } catch (error) {
      console.error(`选择图片失败: ${error instanceof Error ? error.message : String(error)}`);
      promptAction.showToast({ message: '选择图片失败,请重试' });
    }
  }
  
  // 拍摄照片
  private async takePhoto(): Promise<void> {
    if (this.selectedImages.length >= this.maxImageCount) {
      promptAction.showToast({ message: `最多只能上传${this.maxImageCount}张图片` });
      return;
    }
    
    try {
      const photoPath = await takePhotoWithCamera();
      
      if (photoPath) {
        // 压缩图片
        const processedPath = await compressImage(photoPath);
        this.selectedImages.push(processedPath);
        
        // 上传图片
        this.uploadSelectedImages();
      }
    } catch (error) {
      console.error(`拍照失败: ${error instanceof Error ? error.message : String(error)}`);
      promptAction.showToast({ message: '拍照失败,请重试' });
    }
  }
  
  // 移除已选图片
  private removeImage(index: number): void {
    const updatedImages = [...this.selectedImages];
    updatedImages.splice(index, 1);
    this.selectedImages = updatedImages;
    
    // 如果存在回调,通知选择变化
    if (this.onImagesSelected) {
      // 此处不需要重新上传,因为已经上传过的图片URL已经被记录
      this.onImagesSelected([]);
    }
  }
  
  // 上传选中的图片
  private async uploadSelectedImages(): Promise<void> {
    try {
      this.uploading = true;
      this.progress = 0;
      
      // 模拟进度更新
      const progressTimer = setInterval(() => {
        if (this.progress < 90) {
          this.progress += 10;
        }
      }, 300);
      
      // 上传图片
      const uploadedUrls = await uploadImages(this.selectedImages);
      
      clearInterval(progressTimer);
      this.progress = 100;
      
      // 通知上传完成
      if (this.onImagesSelected) {
        this.onImagesSelected(uploadedUrls);
      }
      
      // 延迟关闭进度条
      setTimeout(() => {
        this.uploading = false;
      }, 500);
    } catch (error) {
      this.uploading = false;
      console.error(`上传图片失败: ${error instanceof Error ? error.message : String(error)}`);
      promptAction.showToast({ message: '上传图片失败,请重试' });
    }
  }
}

图片查看器组件

用于查看和浏览图片的组件:

@Component
export struct ImageViewerComponent {
  @State private currentIndex: number = 0;
  @Prop imageUrls: string[] = [];
  private controller: SwiperController = new SwiperController();
  
  build() {
    Column() {
      // 图片轮播查看器
      Swiper(this.controller) {
        ForEach(this.imageUrls, (url: string) => {
          Image(url)
            .width('100%')
            .height('100%')
            .objectFit(ImageFit.Contain)
        })
      }
      .width('100%')
      .height('90%')
      .loop(true)
      .index(this.currentIndex)
      .indicator(false)
      .onChange((index: number) => {
        this.currentIndex = index;
      })
      
      // 底部指示器和控制按钮
      Row() {
        Button() {
          Image($r('app.media.ic_close'))
            .width(24)
            .height(24)
        }
        .backgroundColor('rgba(0, 0, 0, 0.5)')
        .width(40)
        .height(40)
        .borderRadius(20)
        .onClick(() => {
          // 关闭查看器
        })
        
        Row() {
          ForEach(this.imageUrls, (_, index: number) => {
            Circle()
              .width(8)
              .height(8)
              .margin(4)
              .fill(index === this.currentIndex ? '#FFFFFF' : 'rgba(255, 255, 255, 0.5)')
          })
        }
        .justifyContent(FlexAlign.Center)
        .width('60%')
        
        Text(`${this.currentIndex + 1}/${this.imageUrls.length}`)
          .fontSize(14)
          .fontColor('#FFFFFF')
      }
      .width('100%')
      .height('10%')
      .padding({ left: 16, right: 16 })
      .backgroundColor('rgba(0, 0, 0, 0.3)')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
  }
}

高级功能

图片缓存管理

为提高性能而实现的图片缓存系统:

import fileio from '@ohos.fileio';
import { crypto } from '@ohos.security.crypto';

class ImageCacheManager {
  private static instance: ImageCacheManager;
  private cacheDir: string;
  private memoryCache: Map<string, string> = new Map();
  private maxMemoryCacheSize: number = 20; // 最大内存缓存项数
  
  private constructor() {
    this.cacheDir = `${getContext().cacheDir}/image_cache`;
    this.ensureCacheDirExists();
  }
  
  static getInstance(): ImageCacheManager {
    if (!ImageCacheManager.instance) {
      ImageCacheManager.instance = new ImageCacheManager();
    }
    return ImageCacheManager.instance;
  }
  
  // 确保缓存目录存在
  private async ensureCacheDirExists(): Promise<void> {
    try {
      const exists = await fileio.access(this.cacheDir);
      if (!exists) {
        await fileio.mkdir(this.cacheDir);
      }
    } catch (error) {
      console.error(`创建缓存目录失败: ${error instanceof Error ? error.message : String(error)}`);
    }
  }
  
  // 生成URL的唯一键
  private async generateCacheKey(url: string): Promise<string> {
    const hash = await crypto.sha256(url);
    return hash;
  }
  
  // 获取缓存图片路径
  async getCachedImage(url: string): Promise<string | null> {
    const key = await this.generateCacheKey(url);
    
    // 检查内存缓存
    if (this.memoryCache.has(key)) {
      return this.memoryCache.get(key);
    }
    
    // 检查文件缓存
    const cachePath = `${this.cacheDir}/${key}`;
    try {
      const exists = await fileio.access(cachePath);
      if (exists) {
        // 添加到内存缓存
        this.addToMemoryCache(key, cachePath);
        return cachePath;
      }
    } catch (error) {
      // 文件不存在,继续
    }
    
    return null;
  }
  
  // 缓存图片
  async cacheImage(url: string, imagePath: string): Promise<string> {
    const key = await this.generateCacheKey(url);
    const cachePath = `${this.cacheDir}/${key}`;
    
    try {
      // 复制文件到缓存目录
      await fileio.copy(imagePath, cachePath);
      
      // 添加到内存缓存
      this.addToMemoryCache(key, cachePath);
      
      return cachePath;
    } catch (error) {
      console.error(`缓存图片失败: ${error instanceof Error ? error.message : String(error)}`);
      return imagePath; // 失败时返回原始路径
    }
  }
  
  // 添加到内存缓存
  private addToMemoryCache(key: string, path: string): void {
    // 如果缓存已满,移除最旧的项
    if (this.memoryCache.size >= this.maxMemoryCacheSize) {
      const firstKey = this.memoryCache.keys().next().value;
      this.memoryCache.delete(firstKey);
    }
    
    // 添加新项
    this.memoryCache.set(key, path);
  }
  
  // 清理缓存
  async clearCache(): Promise<void> {
    try {
      // 清理内存缓存
      this.memoryCache.clear();
      
      // 清理文件缓存
      const files = await fileio.listFile(this.cacheDir);
      for (const file of files) {
        try {
          await fileio.unlink(`${this.cacheDir}/${file}`);
        } catch (error) {
          console.error(`删除缓存文件失败: ${file}, 错误: ${error instanceof Error ? error.message : String(error)}`);
        }
      }
      
      console.info('图片缓存已清理');
    } catch (error) {
      console.error(`清理缓存失败: ${error instanceof Error ? error.message : String(error)}`);
    }
  }
}

图片水印添加

为用户上传的图片添加水印功能:

import { image as imageModule } from '@ohos.multimedia.image';

export async function addWatermark(imagePath: string, watermarkText: string): Promise<string> {
  try {
    // 创建图像源
    const imageSource = imageModule.createImageSource(imagePath);
    if (!imageSource) {
      throw new Error('无法创建图像源');
    }
    
    // 获取图像信息
    const imageInfo = await imageSource.getImageInfo();
    const width = imageInfo.size.width;
    const height = imageInfo.size.height;
    
    // 创建画布
    const canvas = new OffscreenCanvas(width, height);
    const ctx = canvas.getContext('2d');
    
    // 绘制原始图像
    const rawImage = await imageSource.createPixelMap();
    ctx.drawImage(rawImage, 0, 0, width, height);
    
    // 绘制水印文本
    ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
    ctx.font = '24px sans-serif';
    ctx.textAlign = 'right';
    ctx.textBaseline = 'bottom';
    ctx.fillText(watermarkText, width - 20, height - 20);
    
    // 获取画布内容
    const imageData = ctx.getImageData(0, 0, width, height);
    
    // 创建新的图像包
    const imagePacker = imageModule.createImagePacker();
    
    // 打包图像数据
    const packOptions = {
      format: imageModule.ImageFormat.JPEG,
      quality: 90
    };
    const packedData = await imagePacker.packing(imageData, packOptions);
    
    // 保存到临时文件
    const tempPath = `${getContext().cacheDir}/watermarked_${Date.now()}.jpg`;
    await fileio.writeArrayBuffer(tempPath, packedData);
    
    // 释放资源
    imagePacker.release();
    imageSource.release();
    
    return tempPath;
  } catch (error) {
    console.error(`添加水印失败: ${error instanceof Error ? error.message : String(error)}`);
    return imagePath; // 失败时返回原始图片路径
  }
}

系统集成

图片处理系统与其他系统模块,如社区互动和梦想管理,紧密集成。例如,当用户创建梦想或分享进度时,可以无缝地添加图片作为视觉记录。同时,缓存管理和优化策略确保即使在网络条件不佳或设备资源受限的情况下,图像处理操作也能高效执行。

图片处理系统通过提供易用且强大的图像管理功能,丰富了用户在梦想生活规划师应用中的体验,使他们能够更生动、更直观地记录和分享自己的梦想历程。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐