Flutter 像素编辑器#04 | 导入导出图像

2024-05-29 12:11:54 浏览数 (2)

1. 本文目的

本系列,将通过 Flutter 实现一个全平台的像素编辑器应用。源码见开源项目 【pix_editor】。在前三篇中,我们已经完成了一个简易的图像编辑器,并且简单引入了图层的概念,支持切换图层显示不同的像素画面。

  • 《Flutter 像素编辑器#01 | 像素网格》
  • 《Flutter 像素编辑器#02 | 配置编辑》
  • 《Flutter 像素编辑器#03 | 像素图层》

本文的目标两个:

1. 支持导入一张图像,将其像素化地展示在界面中:

01.gif01.gif

2. 像素点可编辑,编辑完成后,可以将图片进行导出到对应文件夹:

02.gif02.gif
image.pngimage.png

2. 图像的导入

图像本质上是由一个个像素点构成的二维空间点阵。在像素编辑器中,每个单元格记录着一份像素信息,我们需要根据网格行列数,对图像的像素信息进行采样。行列数会直接决定当前区域中像素信息相对于原图像的的完整程度。比如下面分别是 16*1632*3264*64 的网格采样同一图像的呈现效果:

16*16

32*32

64*64

当前需求的关键点在于:如何获取原图像的所有像素点信息,然后根据像素点映射生成 PixCell 列表数据进行呈现。此时需要依赖 image类库。 现在在业务逻辑对象 PixPaintLogic 中增加一个 setImage 方法,传入用户选择的文件路径,使用 img.decodeImage 方法解码图片生成 img.Image 对象:

代码语言:javascript复制
---->[lib/bloc/pix_paint_logic.dart]----
import 'package:image/image.dart' as img;

void setImage(String filePath) async {
  File file = File(filePath);
  img.Image? pixImage = img.decodeImage(await file.readAsBytes());
  if(pixImage==null){
    return;
  }
  setPixByImage(pixImage);
}

setPixByImage 方法中处理核心逻辑:遍历网格的行列数,从 image 中采样对应的像素值。其中 rate 标识格点像素相较于真实像素的坐标缩放比例,也就是像素采样的间隔。将非透明色的像素点,加入到 PixCell 列表中:

代码语言:javascript复制
void setPixByImage(img.Image image) {
  List<PixCell> cells = [];
  int minSize = min(image.width, image.height);
  int minCount = min(row, column);
  int count = minSize.clamp(0, minCount);
  double rate = minSize / count;
  
  for (int x = 0; x < count; x  ) {
    for (int y = 0; y < count; y  ) {
      var pixel = image.getPixel((y * rate).toInt(), (x * rate).toInt());
      var color = Color.fromARGB(
          pixel.a.toInt(), pixel.r.toInt(), pixel.g.toInt(), pixel.b.toInt());
      if (color != Colors.transparent) {
        cells.add(PixCell(color: color, position: (y, x)));
      }
    }
  }
  setPixCells(cells);
}

最后通过 setPixCells 方法,将生成的 PixCell 列表加入到状态数据中,并发送更新通知,让画板进行变化:

代码语言:javascript复制
void setPixCells(List<PixCell> cells) {
  pixCells.clear();
  pixCells.addAll(cells);
  notifyListeners();
  activePixLayer.update();
}

3. 图像的导出

本来是想通过 Canvas 进行绘制导出图片的,但是效果并不理想,因为 Flutter 的 1px 问题,并不适合绘制细小的像素。 image类库 中提供了像素级的操作,直接生成 png 图像:

image.pngimage.png

如下所示,先创建一个 pixLayer 网格宽高的 img.Image 图像,通过数为 4 个,默认是 3 没有透明度。然后遍历 pixLayer#pixCells 为生成的 image 对象设置像素信息。最后只需要 img.encodePng(image) 即可进行编码得到字节列表数据:

代码语言:javascript复制
PixPaintLogic paintLogic = PixPaintScope.read(context);
PixLayer pixLayer = paintLogic.activePixLayer;

final image = img.Image(
  width: pixLayer.row,
  height: pixLayer.column,
  numChannels: 4,
);

for (PixCell pix in pixLayer.pixCells) {
  Color color = pix.color;
  image.setPixelRgba(
    pix.position.$1,
    pix.position.$2,
    color.red,
    color.green,
    color.blue,
    color.alpha,
  );
}

final Uint8List byteData = img.encodePng(image);

有了字节列表数据之后,通过 FilePicker.platform.saveFile 选择保存文件,然后写入文件即可:

代码语言:javascript复制
String? result = await FilePicker.platform.saveFile(type: FileType.image);
if (result != null) {
  File file = File(result);
  await file.writeAsBytes(byteData);
}

到这里,导入导出图像的功能就基本完成了,这样像素编辑的基本功能就能运转了,但目前只支持正方形的图片,后续会继续优化,支持矩形的网格。可以多多关注 【pix_editor】 的发展。

0 人点赞