Files
abc-lee-ComfyUI-InpaintRegi…/docs/HANDOFF.md
T
InpaintRegionEditor Dev 107e992cfe docs: 更新文档,准备发布 v1.4
- README: 移除开发中标记,更新功能列表
- CHANGELOG: 添加 v1.4.0 版本记录
- HANDOFF: 更新版本历史
- gitignore: 添加 *.bak 排除临时备份
- 代码注释: 掏窟窿 -> 透明区域 RGB 数据丢失
2026-03-03 21:56:48 +08:00

21 KiB
Raw Blame History

InpaintRegionEditor 项目交接文档

版本:1.4 | 日期:2026-03-03 交接对象:开发团队 交接人:项目经理 Agent


一、项目概述

1.1 项目定位

InpaintRegionEditor 是一个 ComfyUI 自定义节点,用于解决局部重绘(inpaint)工作流中的两个核心问题:

  1. 图像编辑:ComfyUI 自身没有图像编辑功能,需要集成 Photopea 实现液化、仿制图章等 PS 级功能
  2. 蒙版编辑:支持羽化效果的蒙版编辑,使用 Photopea 双图层模式
  3. 选区调整:原有的扩散选区功能无法手动调整位置,导致边缘参考区域错误

1.2 核心价值

原工作流痛点:
├─ 蒙版编辑:ComfyUI 自带工具功能有限,无法处理复杂蒙版(羽化、透明度等)
├─ 图像编辑:完全没有,需要外部 PS 处理后再上传
└─ 选区调整:只能中心扩散,无法手动移动位置

本节点解决方案:
├─ 集成 Photopea:完整的 Photoshop 功能,浏览器内完成编辑
├─ 双模式编辑:编辑图像 / 编辑蒙版(分离操作)
├─ 羽化支持:半透明蒙版自动转换为羽化效果
├─ 可拖动选区:手动调整参考区域位置,确保参考内容准确
└─ 一体化流程:上传 → 编辑 → 选区调整 → 执行,无需切换工具

1.3 节点功能

输入:
  - image: 从 ComfyUI input 目录选择(支持上传、Ctrl+V 粘贴)
  - padding: 扩散像素数(默认 64,范围 0-512)

自动处理:
  1. 读取图像 Alpha 通道作为 MASK
  2. 没有 Alpha → 报错 "请先创建蒙版"
  3. 有 Alpha → 计算蒙版边界框(alpha < 255 都计入)
  4. 选区 = 蒙版边界 + padding
  5. 在图像预览上绘制遮罩 + 选区框(可拖动、可调整大小)

用户操作:
  - 右键 → "编辑图像(Photopea)":编辑图像本身
  - 右键 → "编辑蒙版(Photopea)":双图层蒙版编辑(参考图像 + 蒙版层)
  - 右键 → "Open in MaskEditor":使用 ComfyUI 自带蒙版编辑器
  - 拖动选区框:手动调整参考区域位置
  - 调整选区边缘:拖动边缘/角落调整选区大小
  - 调整 padding 值:改变扩散像素数
  - Ctrl+V:粘贴图片

输出:
  - IMAGE: RGB 图像(不含 Alpha)
  - MASK: 从 Alpha 通道提取
  - region_top, region_left, region_width, region_height: 选区坐标

二、核心需求确认

2.1 蒙版来源(三种)

  1. 用户上传:带 Alpha 通道的 PNG 文件
  2. Photopea 编辑:在 Photopea 里绘制蒙版后保存
  3. 系统工具:使用 ComfyUI 自带蒙版工具绘制后保存

2.2 核心逻辑

if 没有蒙版(Alpha 通道):
    执行时报错:"请先创建蒙版(局部重绘需要蒙版)"
else:
    自动计算选区 = 蒙版边界 + padding
    在预览上绘制遮罩(半透明红)+ 选区框(橙色虚线)
    用户可以拖动选区框位置
    输出 region_* 坐标

2.3 选区概念

概念 定义 作用
遮罩 (Mask) 需要重绘的区域(可能是不规则形状) AI 在这个区域内生成新内容
选区 (Region) 参考区域(矩形框) AI 参考这个区域内的原图内容进行重绘

约束:选区必须 >= 遮罩(选区要完全包含遮罩)

选区计算逻辑:

选区边界 = 遮罩边界框 + padding(向四周扩散)
用户可拖动选区位置,但选区必须始终框住遮罩

三、技术架构

3.1 文件结构

ComfyUI-InpaintRegionEditor/
├── __init__.py                 # 节点注册
├── nodes.py                    # 后端节点逻辑
├── requirements.txt            # 无额外依赖
│
├── web/
│   ├── extension.js            # 扩展入口 + 右键菜单
│   ├── photopea-bridge.js      # Photopea 通信桥接
│   ├── photopea-modal.js       # Photopea 全屏对话框
│   ├── region-box-editor.js    # 选区框编辑器(Canvas + 拖动)
│   └── style.css               # 样式
│
└── docs/
    ├── technical-validation.md # 技术验证清单
    ├── implementation-plan.md  # 详细实现方案
    └── HANDOFF.md              # 本文档

3.2 整体架构

┌─────────────────────────────────────────────────────────────────────┐
│                         ComfyUI 前端                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  InpaintRegionEditor 节点                                      │ │
│  │                                                               │ │
│  │  ┌───────────────────────────────────────────────────────┐   │ │
│  │  │  图像预览区域                                          │   │ │
│  │  │  ┌───────────────────────────────────────────────┐    │   │ │
│  │  │  │  [图像]                                        │    │   │ │
│  │  │  │  ┌──────────────┐                             │    │   │ │
│  │  │  │  │ ████████████ │ ← 遮罩(半透明红)            │    │   │ │
│  │  │  │  │ │          │ │                             │    │   │ │
│  │  │  │  │ │  虚线框   │ │ ← 选区(橙色虚线,可拖动)   │    │   │ │
│  │  │  │  │ │          │ │                             │    │   │ │
│  │  │  │  │ ████████████ │                             │    │   │ │
│  │  │  │  └──────────────┘                             │    │   │ │
│  │  │  └───────────────────────────────────────────────┘    │   │ │
│  │  │                                                        │   │ │
│  │  │  控件:                                                │   │ │
│  │  │  - 图像:[image.png ▼] [上传]                         │   │ │
│  │  │  - 扩散像素:[64] ▼                                   │   │ │
│  │  │  - [🎨 打开 Photopea 编辑]                             │   │ │
│  │  └───────────────────────────────────────────────────────┘   │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                          ↕                                          │
│              隐藏 widget: region_coords (JSON)                     │
│                          ↕                                          │
└─────────────────────────────────────────────────────────────────────┘
                          ↕
┌─────────────────────────────────────────────────────────────────────┐
│                       ComfyUI 后端                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  nodes.py: InpaintRegionEditor.process()                           │
│  1. 加载图像(从 input 目录)                                        │
│  2. 提取 Alpha 通道 → MASK                                          │
│  3. 计算遮罩边界框                                                   │
│  4. 计算选区 = 遮罩边界 + padding                                   │
│  5. 验证:选区 >= 遮罩                                              │
│  6. 输出:IMAGE, MASK, region_*                                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
                          ↕
┌─────────────────────────────────────────────────────────────────────┐
│                        Photopea                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  - 通过 iframe 嵌入(全屏模态框)                                    │
│  - postMessage API 通信                                             │
│  - 功能:图像编辑 + 蒙版绘制                                        │
│  - 导出:PNG 图像 + 黑白蒙版图                                       │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.3 数据流

1. 用户上传图像
   └─▶ POST /upload/image → input/image.png

2. 节点加载图像
   └─▶ 读取 Alpha 通道 → MASK
   └─▶ 计算遮罩边界 → 选区
   └─▶ 前端绘制:遮罩 + 选区框

3. 用户编辑(可选)
   └─▶ 右键 → 打开 Photopea
   └─▶ 编辑图像/蒙版
   └─▶ 保存 → 上传新文件
   └─▶ 更新节点 widget.value

4. 用户调整选区(可选)
   └─▶ 拖动选区框
   └─▶ 更新 region_coords (hidden widget)

5. 执行工作流
   └─▶ 后端读取图像 + region_coords
   └─▶ 输出:IMAGE, MASK, region_*

四、关键技术点

4.1 蒙版处理

提取逻辑(nodes.py):

if 'A' in img.getbands():
    mask = np.array(img.getchannel('A')).astype(np.float32) / 255.0
    mask = 1.0 - mask  # ComfyUI 约定:1=遮罩区域,0=背景
else:
    raise ValueError("请先创建蒙版")

格式约定:

  • PNG Alpha = 0(透明) → mask = 1.0(遮罩区域)
  • PNG Alpha = 255(不透明) → mask = 0.0(背景)

4.2 选区计算

def _calculate_region_rect(mask_bounds, padding):
    """根据遮罩边界 + padding 计算选区"""
    return {
        'x': mask_bounds['x'] - padding,
        'y': mask_bounds['y'] - padding,
        'width': mask_bounds['width'] + padding * 2,
        'height': mask_bounds['height'] + padding * 2
    }

约束验证:

if (region_rect['width'] < mask_bounds['width'] or 
    region_rect['height'] < mask_bounds['height']):
    raise ValueError("选区必须 >= 遮罩")

4.3 Photopea 集成

通信机制:

// 发送消息到 Photopea
photopeaWindow.postMessage('app.activeDocument.saveToOE("png");', "*");

// 接收响应(验证 origin)
window.addEventListener("message", (event) => {
    if (event.origin !== "https://www.photopea.com") return;
    // 处理响应...
});

待验证功能:

  1. 能否单独导出蒙版?(layer.mask 已知返回 null)
  2. 能否同时加载原图 + 蒙版?
  3. 能否检测用户是否画了蒙版?

参考 workaround(DEVLOG 中提到):

// 虽然 layer.mask 返回 null,但这行代码可能工作
doc.activeChannel = layer.mask;
doc.selection.selectAll();
doc.selection.copy();

4.4 选区框绘制

参考实现:ComfyUI-Impact-Pack/js/mask-rect-area.js

核心功能:

  • 自定义 Canvas Widget
  • 绘制遮罩(半透明红)
  • 绘制选区框(橙色虚线)
  • 鼠标拖动交互
  • 同步坐标到 widget

五、待验证技术点

5.1 高优先级(决定方案可行性)

编号 测试项 目的 状态
P1 Photopea 同时加载原图 + 蒙版 确定工作流 ❓ 待测试
P2 Photopea 单独导出蒙版 确定保存逻辑 ❓ 待测试
P3 Photopea 检测是否有蒙版 确定错误提示 ❓ 待测试

5.2 中优先级(影响用户体验)

编号 测试项 目的 状态
M1 Canvas 选区框绘制 参考 Impact-Pack ✅ 有参考代码
M2 拖动交互实现 鼠标事件监听 ✅ 有参考代码
M3 坐标同步机制 hidden widget ✅ 已验证

5.3 低优先级(可迭代优化)

编号 测试项 目的 状态
L1 两次导出上传 图像 + 蒙版分开保存 ❓ 待测试
L2 后端合成 PNG 两张图合成带 Alpha 的 PNG ✅ 简单
L3 UI 样式优化 美观度提升 - 后期

六、实现路线图

Phase 1:后端基础(1 天)

任务:

  • 更新 nodes.py 实现核心逻辑
  • 测试蒙版提取(从 Alpha 通道)
  • 测试选区计算(遮罩边界 + padding)
  • 测试错误提示(没有蒙版时报错)

交付物:

  • 可运行的后端节点
  • 单元测试脚本

Phase 2:选区 UI(2 天)

任务:

  • 实现 region-box-editor.js
  • 在节点上绘制遮罩预览
  • 在节点上绘制选区框(虚线)
  • 实现拖动交互
  • 约束验证(选区 >= 遮罩)

参考:ComfyUI-Impact-Pack/js/mask-rect-area.js

交付物:

  • 可在节点上显示并拖动选区框

Phase 3:Photopea 集成(2-3 天)

任务:

  • 实现 photopea-modal.js
  • 实现 photopea-bridge.js
  • 测试图像加载(&channel=rgb 参数)
  • 测试图像导出
  • 测试蒙版导出(关键!)
  • 上传回 ComfyUI

交付物:

  • 可打开 Photopea 编辑
  • 可保存回节点

Phase 4:集成测试(1 天)

任务:

  • 完整工作流测试
  • 修复 bug
  • 编写 README

交付物:

  • 可发布的完整版本

七、已知风险与备选方案

7.1 Photopea API 限制

问题:layer.mask 总是返回 null(官方已知,Issue #7341)

影响:无法通过脚本访问图层蒙版

备选方案:

  1. 用 workaround 尝试导出(doc.activeChannel = layer.mask)
  2. 如果不行,用户只能用其他方式创建蒙版(系统工具/上传)
  3. 或者在 Photopea 里手动合成到 Alpha 通道后导出

7.2 蒙版语义差异

问题:

  • Photopea 蒙版:白色 = 显示(非遮罩),黑色 = 隐藏(遮罩)
  • ComfyUI Mask:白色 = 遮罩区域,黑色 = 背景

解决:导出时反转

7.3 性能问题

问题:大图片(>2048x2048)可能导致 Photopea 卡顿

解决:

  • 提示用户使用合适尺寸
  • 可选:前端缩放后编辑

八、参考资源

8.1 代码参考

文件 位置 用途
mask-rect-area.js E:\ComfyUI\custom_nodes\ComfyUI-Impact-Pack\js\ 选区框绘制参考
nodes.py (LoadImage) E:\ComfyUI\nodes.py (line 1702) 图像加载参考
server.py E:\ComfyUI\server.py 上传 API 参考

8.2 文档参考

文档 说明
Photopea API 官方 API 文档
technical-validation.md 技术验证清单
implementation-plan.md 详细实现方案
DEVLOG.md 开发日志(包含历史问题记录)

8.3 外部参考

项目 说明
sd-webui-photopea-embed Stable Diffusion WebUI 的 Photopea 集成
ComfyUI-Impact-Pack 选区框实现参考

九、团队协作建议

9.1 分工建议

后端开发(1 人):

  • nodes.py 实现
  • 蒙版提取逻辑
  • 选区计算逻辑

前端开发(1 人):

  • Canvas 选区框绘制
  • 拖动交互实现
  • Photopea 集成

测试(1 人):

  • Photopea 功能验证
  • 完整工作流测试
  • Bug 报告

9.2 沟通要点

  1. Photopea 验证结果 决定技术路线

    • 如果能单独导出蒙版 → 方案 A
    • 如果不能 → 方案 B(用户手动合成到 Alpha)
  2. 选区 UI 复杂度 可能被低估

    • 建议先用 Impact-Pack 代码验证可行性
    • 再考虑自定义需求
  3. 错误提示要友好

    • 没有蒙版时告诉用户三种创建方式
    • 选区太小时提示最小尺寸

十、验收标准

10.1 功能验收

  • 用户上传带 Alpha 的 PNG → 正确显示遮罩 + 选区
  • 用户上传不带 Alpha 的 PNG → 执行时报错(友好提示)
  • 拖动选区框 → 坐标实时更新
  • 调整 padding → 选区自动重算
  • 打开 Photopea → 可以编辑图像
  • Photopea 保存 → 图像同步到节点
  • 输出 region_* → 下游节点可用

10.2 性能验收

  • 加载 1024x1024 图像 < 2 秒
  • 拖动选区框流畅(60fps)
  • Photopea 打开 < 5 秒(首次加载缓存后)

10.3 兼容性验收

  • Windows / Linux / macOS
  • Chrome / Firefox / Edge
  • ComfyUI 最新版本

十一、联系与反馈

11.1 问题记录

开发过程中遇到的任何问题,请记录到:

  • docs/DEVLOG.md(开发日志)
  • GitHub Issues(如果有仓库)

11.2 关键决策记录

任何偏离本方案的技术决策,请记录:

  • 决策内容
  • 决策原因
  • 决策日期

十二、附录

12.1 术语表

术语 定义
Mask(遮罩) 定义重绘区域,AI 在这个区域内生成新内容
Region(选区) 定义参考区域,AI 参考这个区域内的原图内容
Padding(扩散) 选区相对于遮罩边界的扩散像素数
Alpha 通道 PNG 的透明度通道,用于存储蒙版
羽化 蒙版边缘的半透明过渡效果(alpha = 1-254)

12.2 版本历史

版本 日期 说明
0.1 2026-02-27 初始版本(简化版,仅 Photopea 集成)
1.0 2026-03-02 需求最终确认,编写 handoff 文档
1.1 2026-03-03 Photopea 蒙版编辑功能,修复透明区域 RGB 数据丢失问题
1.2 2026-03-03 选区调整大小、光标变化、Ctrl+V 粘贴、MaskEditor 集成
1.3 2026-03-03 MaskEditor clipspace 支持、选区坐标同步、羽化效果
1.4 2026-03-03 多语言支持(中英文)、修复编辑图像透明区域 RGB 数据丢失、修复选区坐标自动更新

十三、关键技术实现

13.1 Canvas Premultiplied Alpha 问题

问题描述: 当 Canvas 通过 drawImage() 绘制带有 Alpha < 255 的 PNG 图像时,浏览器会应用 premultiplied alpha,导致 Alpha = 0 的像素 RGB 数据丢失。

解决方案:

  1. 加载时:使用 ComfyUI /view API 的 channel=rgb 和 channel=a 参数分别获取 RGB 和 Alpha 数据
  2. 保存时:使用 UPNG.js 库直接编码 RGBA 数据,绑过 Canvas 的 premultiplied alpha

13.2 Photopea 蒙版编辑流程

1. 加载图像
   ├─ 获取 RGB 数据(channel=rgb)→ 完整 RGB,无窟窿
   └─ 获取 Alpha 数据(channel=a)→ 用于创建蒙版层

2. 创建双图层文档
   ├─ 底层:参考图像(完整 RGB)
   └─ 顶层:蒙版层(用户在此绘制黑色 = 遮罩)

3. 保存蒙版
   ├─ 隐藏参考图像层
   ├─ 只导出蒙版层
   ├─ 获取原图 RGB(channel=rgb)
   └─ 合并蒙版到 Alpha 通道(UPNG.js 编码)

13.3 羽化逻辑

蒙版层(用户绘制):
- 黑色不透明(alpha=255)→ 遮罩 → 最终 alpha=0
- 黑色半透明(alpha=128)→ 羽化 → 最终 alpha=128
- 完全透明(alpha=0)→ 不遮罩 → 最终 alpha=255

公式:最终 alpha = 255 - 蒙版层alpha

文档创建时间:2026-03-02
最后更新时间:2026-03-03
基于与用户的多次讨论整理
交接完成标志:开发团队确认收到并理解本文档