558 字
3 分钟
Flutter 实现抠图效果
2024-12-10

本文将介绍如何在 Flutter 中利用原始图像和掩码图像合并,实现抠图效果。

记得以前看过一篇文章,bilibili 的弹幕会自动躲开人物头部就是这样实现的?(后端会给 mask)

基本原理#

抠图的核心是使用一张掩码图像来决定原始图像的哪些部分需要保留,哪些部分需要去除。掩码图像通常是黑白或灰度图,白色区域表示需要保留的部分,黑色区域表示需要移除的背景。

通过将掩码应用到原始图像上,我们可以生成一张只包含主体部分的新图像。

实现步骤#

1. 加载原始图像和掩码图像#

首先,使用 ui.instantiateImageCodec 方法将图像数据加载为 ui.Image 对象:

// 加载原始图像
final ui.Codec srcCodec = await ui.instantiateImageCodec(srcData.buffer.asUint8List());
final ui.FrameInfo srcFrame = await srcCodec.getNextFrame();
final ui.Image srcImage = srcFrame.image;

// 加载掩码图像
final ui.Codec maskCodec = await ui.instantiateImageCodec(maskData.buffer.asUint8List());
final ui.FrameInfo maskFrame = await maskCodec.getNextFrame();
final ui.Image maskImage = maskFrame.image;

2. 创建画布并绘制原始图像#

使用 ui.PictureRecorderCanvas 创建一个画布,并在其上绘制原始图像:

final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);

// 绘制原始图像
final paint = Paint();
canvas.drawImage(srcImage, Offset.zero, paint);

3. 应用掩码图像#

设置一个 Paint 对象,使用 BlendMode.dstOut 混合模式和 ColorFilter,将掩码应用到原始图像上:

final maskPaint = Paint()
  ..blendMode = BlendMode.dstOut
  ..colorFilter = const ColorFilter.matrix([
    0, 0, 0, 0, 0, // R 通道
    0, 0, 0, 0, 0, // G 通道
    0, 0, 0, 0, 0, // B 通道
    -1, -1, -1, 1, 1, // Alpha 通道
  ]);

// 应用掩码图像
canvas.drawImage(maskImage, Offset.zero, maskPaint);

4. 生成新图像并保存#

结束绘制后,生成新的图像并保存到文件:

// 结束录制
final picture = recorder.endRecording();

// 生成新图像
final ui.Image finalImage = await picture.toImage(srcImage.width, srcImage.height);
final ByteData? byteData = await finalImage.toByteData(format: ui.ImageByteFormat.png);

if (byteData != null) {
  final file = File(outputPath);
  await file.writeAsBytes(byteData.buffer.asUint8List());
}

注意事项#

  • 图像尺寸匹配:确保原始图像和掩码图像的尺寸一致,以避免合成时的错位。
  • 掩码图像格式:掩码应为黑白或灰度图,表示透明和不透明区域。
  • 性能优化:图像处理可能比较耗时,建议在异步方法中执行,避免阻塞 UI 线程。
Flutter 实现抠图效果
https://blog.lpkt.cn/posts/flutter-cutout/
作者
lollipopkit
发布于
2024-12-10
许可协议
CC BY-NC-SA 4.0