2 个回答

在HarmonyOS Next中,你可以使用图像处理API来实现将一张图片自动切割为九宫图并保存的功能。以下是实现步骤和示例代码:

实现方案

  1. 使用Image组件和Canvas进行切割
import image from '@ohos.multimedia.image';
import fs from '@ohos.file.fs';

async function splitImageToNineGrid(uri: string, outputDir: string) {
  try {
    // 1. 创建ImageSource实例
    const imageSource = image.createImageSource(uri);
    
    // 2. 获取图片信息
    const imageInfo = await imageSource.getImageInfo();
    const width = imageInfo.size.width;
    const height = imageInfo.size.height;
    
    // 3. 计算每个小图的尺寸
    const partWidth = Math.floor(width / 3);
    const partHeight = Math.floor(height / 3);
    
    // 4. 创建PixelMap用于存储原始图片数据
    const pixelMap = await imageSource.createPixelMap();
    
    // 5. 创建ImagePacker用于保存图片
    const imagePacker = image.createImagePacker();
    
    // 6. 循环切割并保存9个部分
    for (let row = 0; row < 3; row++) {
      for (let col = 0; col < 3; col++) {
        // 计算当前小图的区域
        const region = {
          x: col * partWidth,
          y: row * partHeight,
          width: partWidth,
          height: partHeight
        };
        
        // 从原图中提取当前区域
        const options = {
          editable: true,
          pixelFormat: 3, // RGBA_8888
          size: { width: partWidth, height: partHeight },
          region: region
        };
        
        const partPixelMap = await image.createPixelMapFromPixelMap(pixelMap, options);
        
        // 保存到文件
        const outputPath = `${outputDir}/part_${row}_${col}.jpg`;
        const packOpts = {
          format: "image/jpeg",
          quality: 100
        };
        
        const arrayBuffer = await imagePacker.packing(partPixelMap, packOpts);
        await fs.writeFile(output
  1. 使用方式
// 示例调用
const context = getContext(this) as common.UIAbilityContext;
const tempDir = context.filesDir + '/nine_grid';

// 确保目录存在
if (!fs.accessSync(tempDir)) {
  fs.mkdirSync(tempDir);
}

// 调用函数 (替换为你的图片URI)
splitImageToNineGrid('file://path/to/your/image.jpg', tempDir);

注意事项
权限申请:确保你的应用有文件读写权限,在module.json5中添加:

"requestPermissions": [
  {
    "name": "ohos.permission.READ_MEDIA",
    "reason": "需要读取图片"
  },
  {
    "name": "ohos.permission.WRITE_MEDIA",
    "reason": "需要保存图片"
  }
]

性能考虑:大图片处理可能会消耗较多内存,建议:

先对图片进行适当压缩

在后台线程进行处理

添加进度提示

输出格式:可以根据需要调整输出格式(JPEG/PNG)和质量

错误处理:实际应用中应添加更完善的错误处理和用户反馈

首先需要在 module.json5 中添加读写图片的权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "需要读取图片"
      },
      {
        "name": "ohos.permission.WRITE_MEDIA",
        "reason": "需要保存图片"
      }
    ]
  }
}

然后使用以下代码实现图片切割和保存功能:

import fs from '@ohos.file.fs';
import image from '@ohos.multimedia.image';
import { common } from '@kit.AbilityKit';
import promptAction from '@kit.ArkUI';

// 定义拼图组件接口
interface PuzzlePiece {
  // 拼图块的像素地图
  pixelMap: image.PixelMap;
  // 原始图片中的索引位置
  originalIndex: number;
}

// 使用装饰器定义页面组件
@Entry
@Component
struct ImageCutPage {
  // 状态变量:选中图片的URI
  @State imgUri: string = '';
  // 状态变量:存储拼图块的数组
  @State puzzlePieces: Array<PuzzlePiece> = [];

  // 弹出图片选择器方法
  async openPicker() {
    try {
      // 设置图片选择器选项
      const photoSelectOptions = new picker.PhotoSelectOptions();
      // 限制只能选择一张图片
      photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      // 创建并实例化图片选择器
      const photoViewPicker = new picker.PhotoViewPicker();
      // 选择图片并获取图片URI
      let uris: picker.PhotoSelectResult = await photoViewPicker.select(photoSelectOptions);
      if (!uris || uris.photoUris.length === 0) return;
      // 获取选中图片的第一张URI
      let uri: string = uris.photoUris[0];

      // 更新状态变量:设置显示图片的URI
      this.imgUri = uri;
      // 图片更改时触发的方法
      this.imgChange();
    } catch (e) {
      console.error('openPicker', JSON.stringify(e));
    }
  }

  // 图片更改处理方法
  async imgChange() {
    try {
      // 创建图片源对象
      const imageSource: image.ImageSource = image.createImageSource(this.imgUri);
      // 图片解码选项
      let decodingOptions: image.DecodingOptions = {
        editable: true,
        desiredPixelFormat: 3,
      };
      // 创建像素地图
      let mPixelMap: image.PixelMap = await imageSource.createPixelMap(decodingOptions);
      // 获取图片信息
      let mImageInfo: image.ImageInfo = await mPixelMap.getImageInfo();

      // 计算每个拼图块的大小
      const cropSize: image.Size = {
        width: mImageInfo.size.width / 3,
        height: mImageInfo.size.height / 3,
      };

      // 清空已有拼图块数据
      this.puzzlePieces.splice(0);

      // 遍历图片生成9个拼图块
      let count = 0;
      for (let row = 0; row < 3; row++) {
        for (let col = 0; col < 3; col++) {
          // 创建基于原图的新图片源
          const imageSource = image.createImageSource(this.imgUri);
          // 创建新像素地图
          let mPixelMap = await imageSource.createPixelMap(decodingOptions);
          // 计算裁剪区域
          const cropRect: image.Region = {
            x: col * cropSize.width,
            y: row * cropSize.height,
            size: cropSize,
          };
          // 裁剪像素地图
          await mPixelMap.crop(cropRect);

          // 创建并添加拼图块至数组
          const piece: PuzzlePiece = {
            pixelMap: mPixelMap,
            originalIndex: count++,
          };
          this.puzzlePieces.push(piece);

          // 保存裁剪后的图片
          this.saveCutImage(mPixelMap, count);
        }
      }
    } catch (e) {
      console.error('imgChange', JSON.stringify(e));
    }
  }

  // 保存裁剪后的图片
  async saveCutImage(pixelMap: image.PixelMap, index: number) {
    try {
      const imagePackerApi: image.ImagePacker = image.createImagePacker();
      const packOpts: image.PackingOption = { format: 'image/jpeg', quality: 80 };
      const buffer: ArrayBuffer = await imagePackerApi.packing(pixelMap, packOpts);

      let context = getContext(this) as common.UIAbilityContext;
      let newUrl = context.filesDir + '/cutImage' + index + '.jpg';

      let file: fs.File = fs.openSync(newUrl, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      await fs.writeSync(file.fd, buffer);
      fs.closeSync(file);

      // 如果需要将图片保存到图库,可以使用PhotoAccessHelper模块
      /* if (this.phAccessHelper === null) {
        return;
      }
      let options: photoAccessHelper.CreateOptions = {
        title: new Date().getTime().toString()
      };
      let photoUri: string = await this.phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, 'jpg', options);
      let file: fileIo.File = fileIo.openSync(photoUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
      await fileIo.write(file.fd, buffer);
      fileIo.closeSync(file); */
    } catch (err) {
      console.error(err)
    }
  }

  // 构建UI界面
  build() {
    Column() {
      // 添加选择图片按钮,点击后调用打开图片选择器方法
      Button('选择图片→').onClick(() => {
        this.openPicker();
      });

      // 如果有拼图块,则显示游戏区
      if (this.puzzlePieces.length > 0) {
        Text('切割后的九宫格图片↓');
        // 游戏区域采用网格布局
        Grid() {
          // 遍历所有拼图块并创建网格项
          ForEach(this.puzzlePieces, (item: PuzzlePiece, index: number) => {
            GridItem() {
              // 显示拼图块图像
              Image(item.pixelMap)
                .width('100lpx')
                .height('100lpx')
                .margin('5lpx');
            }
          }) // End of ForEach
        } // End of Grid
        .backgroundColor("#fafafa"); // 设置网格背景色
      }
    } // End of Column
    .width('100%'); // 设置列宽度为100%
  }
}