1 个回答

下面我将提供一个完整的实现方案,包含相册多图选择、拖动排序和图片合成功能。

  1. 页面布局实现 (index.ets)
import image from '@ohos.multimedia.image';
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct ImageComposer {
  @State selectedImages: Array<ImageItem> = []
  @State composing: boolean = false
  @State resultImage: PixelMap | null = null

  build() {
    Column() {
      // 顶部操作按钮
      Row() {
        Button('选择图片')
          .onClick(() => this.pickImages())
        
        Button('合成图片')
          .onClick(() => this.composeImages())
          .margin({ left: 10 })
          .enabled(this.selectedImages.length > 0 && !this.composing)
      }
      .margin({ top: 10, bottom: 10 })

      // 图片列表(可拖动排序)
      List({ space: 10 }) {
        ForEach(this.selectedImages, (item: ImageItem, index: number) => {
          ListItem() {
            Stack() {
              // 图片显示
              Image(item.pixelMap)
                .width(80)
                .height(80)
                .borderRadius(5)
              
              // 删除按钮
              Image($r('app.media.ic_close'))
                .width(20)
                .height(20)
                .position({ x: 70, y: 0 })
                .onClick(() => this.removeImage(index))
            }
          }
          .width(80)
          .height(80)
          // 拖动排序相关手势
          .gesture(
            GestureGroup(GestureMode.Sequence,
              LongPressGesture()
                .onAction(() => { /* 长按反馈 */ }),
              PanGesture()
                .onActionStart(() => { /* 拖动开始 */ })
                .onActionUpdate((event: GestureEvent) => {
                  // 更新拖动位置
                })
                .onActionEnd(() => {
                  // 完成排序
                })
                .onActionCancel(() => { /* 取消 */ })
            )
          )
        }, (item: ImageItem) => item.uri)
      }
      .layoutWeight(1)
      .width('100%')
      .height('60%')
      .border({ width: 1, color: '#ccc' })

      // 合成结果展示
      if (this.resultImage) {
        Image(this.resultImage)
          .width('90%')
          .height(200)
          .margin({ top: 20 })
      }
    }
    .width('100%')
    .height('100%')
    .padding(10)
  }

  // 从相册选择图片
  async pickImages() {
    try {
      const photoSelectOptions = new picker.PhotoSelectOptions();
      photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 9; // 最多选择9张
      
      const photoPicker = new picker.PhotoViewPicker();
      const result = await photoPicker.select(photoSelectOptions);
      
      if (result && result.photoUris) {
        for (const uri of result.photoUris) {
          const pixelMap = await this.loadImage(uri);
          this.selectedImages.push({ uri, pixelMap });
        }
        this.selectedImages = [...this.selectedImages]; // 触发UI更新
      }
    } catch (error) {
      console.error(`选择图片失败: ${(error as BusinessError).message}`);
    }
  }

  // 加载图片为PixelMap
  async loadImage(uri: string): Promise<image.PixelMap> {
    const file = await fs.open(uri, fs.OpenMode.READ_ONLY);
    const imageSource = image.createImageSource(file.fd);
    const pixelMap = await imageSource.createPixelMap();
    fs.closeSync(file);
    return pixelMap;
  }

  // 移除图片
  removeImage(index: number) {
    this.selectedImages.splice(index, 1);
    this.selectedImages = [...this.selectedImages];
  }

  // 合成图片
  async composeImages() {
    if (this.selectedImages.length === 0) return;
    
    this.composing = true;
    
    try {
      // 1. 确定合成图片的尺寸
      const width = this.selectedImages[0].pixelMap.getImageInfo().size.width;
      const height = this.selectedImages[0].pixelMap.getImageInfo().size.height;
      
      // 2. 创建合成画布
      const imagePacker = image.createImagePacker();
      const componentImage = await image.createPixelMap({
        size: {
          width: width * 2, // 示例:两列布局
          height: Math.ceil(this.selectedImages.length / 2) * height // 计算行数
        },
        pixelFormat: image.PixelFormat.RGBA_8888,
        alphaType: image.AlphaType.PREMUL
      });
      
      // 3. 绘制每张图片到画布
      for (let i = 0; i < this.selectedImages.length; i++) {
        const row = Math.floor(i / 2);
        const col = i % 2;
        const x = col * width;
        const y = row * height;
        
        // 这里需要实现将PixelMap绘制到componentImage的指定位置
        await this.drawImageOnCanvas(
          componentImage, 
          this.selectedImages[i].pixelMap, 
          x, 
          y
        );
      }
      
      // 4. 保存结果
      this.resultImage = componentImage;
      
      // 5. 可选:保存到文件
      // await this.saveImage(componentImage);
    } catch (error) {
      console.error(`合成图片失败: ${(error as BusinessError).message}`);
    } finally {
      this.composing = false;
    }
  }

  // 将图片绘制到画布上
  async drawImageOnCanvas(
    canvas: image.PixelMap, 
    source: image.PixelMap, 
    x: number, 
    y: number
  ): Promise<void> {
    // 这里需要实现具体的绘制逻辑
    // 可以使用Canvas API或直接操作像素数据
  }

  // 保存图片到相册
  async saveImage(pixelMap: image.PixelMap): Promise<void> {
    try {
      const imagePacker = image.createImagePacker();
      const packOpts: image.PackingOption = {
        format: "image/jpeg",
        quality: 100
      };
      
      const data = await imagePacker.packing(pixelMap, packOpts);
      
      const savePicker = new picker.PhotoViewPicker();
      const saveOptions = new picker.PhotoSaveOptions();
      saveOptions.newFileNames = ["composed_image.jpg"];
      
      await savePicker.save(saveOptions, data);
    } catch (error) {
      console.error(`保存图片失败: ${(error as BusinessError).message}`);
    }
  }
}

// 图片项数据类型
interface ImageItem {
  uri: string;
  pixelMap: image.PixelMap;
}
  1. 拖动排序实现
    我们需要完善拖动排序功能。修改List部分的gesture代码:
.gesture(
  GestureGroup(GestureMode.Sequence,
    LongPressGesture()
      .onAction(() => {
        // 显示拖动反馈效果
      }),
    PanGesture()
      .onActionStart(() => {
        // 记录初始位置
      })
      .onActionUpdate((event: GestureEvent) => {
        // 更新拖动位置
        // 可以在这里实现拖动时的预览效果
      })
      .onActionEnd(() => {
        // 计算最终位置并重新排序数组
        this.reorderImages(fromIndex, toIndex);
      })
  )
)

添加重新排序的方法:

// 重新排序图片
reorderImages(fromIndex: number, toIndex: number) {
  if (fromIndex === toIndex) return;
  
  const result = [...this.selectedImages];
  const [removed] = result.splice(fromIndex, 1);
  result.splice(toIndex, 0, removed);
  
  this.selectedImages = result;
}
  1. 图片合成优化
    完善drawImageOnCanvas方法,实现真正的图片合成:
async drawImageOnCanvas(
  canvas: image.PixelMap, 
  source: image.PixelMap, 
  x: number, 
  y: number
): Promise<void> {
  try {
    // 1. 获取画布和源图的像素数据
    const canvasInfo = canvas.getImageInfo();
    const sourceInfo = source.getImageInfo();
    
    // 2. 读取像素数据
    const canvasBuffer = new ArrayBuffer(canvasInfo.size.width * canvasInfo.size.height * 4);
    await canvas.readPixelsToBuffer(canvasBuffer);
    const canvasPixels = new Uint8Array(canvasBuffer);
    
    const sourceBuffer = new ArrayBuffer(sourceInfo.size.width * sourceInfo.size.height * 4);
    await source.readPixelsToBuffer(sourceBuffer);
    const sourcePixels = new Uint8Array(sourceBuffer);
    
    // 3. 将源图数据复制到画布指定位置
    for (let row = 0; row < sourceInfo.size.height; row++) {
      for (let col = 0; col < sourceInfo.size.width; col++) {
        const canvasPos = ((y + row) * canvasInfo.size.width + (x + col)) * 4;
        const sourcePos = (row * sourceInfo.size.width + col) * 4;
        
        // 复制RGBA四个通道
        canvasPixels[canvasPos] = sourcePixels[sourcePos];         // R
        canvasPixels[canvasPos + 1] = sourcePixels[sourcePos + 1]; // G
        canvasPixels[canvasPos + 2] = sourcePixels[sourcePos + 2]; // B
        canvasPixels[canvasPos + 3] = sourcePixels[sourcePos + 3]; // A
      }
    }
    
    // 4. 将修改后的像素数据写回画布
    await canvas.writePixelsFromBuffer(canvasPixels.buffer);
  } catch (error) {
    console.error(`绘制图片失败: ${(error as BusinessError).message}`);
    throw error;
  }
}
  1. 添加权限配置
    在module.json5中添加必要权限:
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "需要读取相册图片"
      },
      {
        "name": "ohos.permission.WRITE_MEDIA",
        "reason": "需要保存合成图片"
      }
    ]
  }
}
  1. 功能扩展建议
    合成布局选项:添加网格、横向、纵向等不同布局选项

图片间距和边框:在合成时添加可配置的间距和边框

图片编辑:在合成前允许对每张图片进行裁剪、旋转等编辑

性能优化:对于大图合成,考虑使用Worker线程处理

注意事项
图片处理是内存密集型操作,注意及时释放不再使用的PixelMap资源

拖动排序时考虑添加视觉反馈,提升用户体验

合成大图时可能需要分块处理,避免内存不足

错误处理要完善,特别是文件操作和图片解码/编码过程

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进