1. 本文目的
本系列,将通过 Flutter 实现一个全平台的像素编辑器应用。源码见开源项目 【pix_editor】。在前三篇中,我们已经完成了一个简易的图像编辑器,并且简单引入了图层的概念,支持切换图层显示不同的像素画面。
- 《Flutter 像素编辑器#01 | 像素网格》
- 《Flutter 像素编辑器#02 | 配置编辑》
- 《Flutter 像素编辑器#03 | 像素图层》
本文的目标两个:
1. 支持导入一张图像,将其像素化地展示在界面中:
2. 像素点可编辑,编辑完成后,可以将图片进行导出到对应文件夹:
2. 图像的导入
图像本质上是由一个个像素点构成的二维空间点阵。在像素编辑器中,每个单元格记录着一份像素信息,我们需要根据网格行列数,对图像的像素信息进行采样。行列数会直接决定当前区域中像素信息相对于原图像的的完整程度。比如下面分别是 16*16
、32*32
、64*64
的网格采样同一图像的呈现效果:
16*16 | 32*32 | 64*64 |
---|---|---|
| | |
当前需求的关键点在于:如何获取原图像的所有像素点信息,然后根据像素点映射生成 PixCell
列表数据进行呈现。此时需要依赖 image类库。 现在在业务逻辑对象 PixPaintLogic 中增加一个 setImage
方法,传入用户选择的文件路径,使用 img.decodeImage
方法解码图片生成 img.Image
对象:
---->[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
列表中:
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
列表加入到状态数据中,并发送更新通知,让画板进行变化:
void setPixCells(List<PixCell> cells) {
pixCells.clear();
pixCells.addAll(cells);
notifyListeners();
activePixLayer.update();
}
3. 图像的导出
本来是想通过 Canvas 进行绘制导出图片的,但是效果并不理想,因为 Flutter 的 1px 问题,并不适合绘制细小的像素。 image类库 中提供了像素级的操作,直接生成 png 图像:
如下所示,先创建一个 pixLayer
网格宽高的 img.Image
图像,通过数为 4 个,默认是 3 没有透明度。然后遍历 pixLayer#pixCells
为生成的 image 对象设置像素信息。最后只需要 img.encodePng(image)
即可进行编码得到字节列表数据:
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
选择保存文件,然后写入文件即可:
String? result = await FilePicker.platform.saveFile(type: FileType.image);
if (result != null) {
File file = File(result);
await file.writeAsBytes(byteData);
}
到这里,导入导出图像的功能就基本完成了,这样像素编辑的基本功能就能运转了,但目前只支持正方形的图片,后续会继续优化,支持矩形的网格。可以多多关注 【pix_editor】 的发展。