diff --git a/CHANGELOG.md b/CHANGELOG.md index 0afca3e..a9b5267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.6.0] - 2026-03-04 + +### Fixed +- **保留手动调整的参考区**:图像重新加载、关闭编辑器窗口后,优先恢复 `region_coords` 中保存的手动选区,不再重置回 padding 计算值 +- **padding 变化时同步 widget**:调整 padding 后同步更新 `region_coords`,保证前端显示与后端输出一致 + +### Changed +- **允许无蒙版运行**:没有 Alpha 通道或蒙版为空时不再报错,输出空蒙版并把参考区设为整张图像,使节点可作为纯图像输入使用 + ## [1.5.0] - 2026-03-04 ### Added diff --git a/nodes.py b/nodes.py index 627c208..6f273dd 100644 --- a/nodes.py +++ b/nodes.py @@ -75,13 +75,8 @@ class InpaintRegionEditor: mask = np.array(img.getchannel("A")).astype(np.float32) / 255.0 mask = 1.0 - mask # ComfyUI 约定:1=遮罩区域 else: - raise ValueError( - "Please create a mask first (inpainting requires a mask)\n" - "You can create one by:\n" - "1. Upload a PNG with Alpha channel\n" - "2. Edit in Photopea and save\n" - "3. Use ComfyUI's built-in mask editor" - ) + # 没有 Alpha 通道时允许作为纯图像输入,输出空蒙版 + mask = np.zeros((img_height, img_width), dtype=np.float32) # 计算蒙版边界框 mask_bounds = self._calculate_mask_bounds(mask) @@ -94,11 +89,20 @@ class InpaintRegionEditor: except: pass - if not region_rect: + # 没有有效蒙版时直接返回全图作为参考区,不再强制要求绘制蒙版 + has_mask = mask_bounds["width"] > 0 and mask_bounds["height"] > 0 + if not has_mask: + region_rect = { + "x": 0, + "y": 0, + "width": img_width, + "height": img_height, + } + elif not region_rect: region_rect = self._calculate_region_rect(mask_bounds, padding) - # 验证 - if ( + # 只有在有蒙版时才验证选区必须包含蒙版 + if has_mask and ( region_rect["width"] < mask_bounds["width"] or region_rect["height"] < mask_bounds["height"] ): diff --git a/pyproject.toml b/pyproject.toml index dff5d82..fbc76a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-inpaint-region-editor" description = "A ComfyUI custom node for inpainting workflows with integrated Photopea editing and adjustable region selection." -version = "1.5.1" +version = "1.6.0" license = { file = "LICENSE" } dependencies = [] diff --git a/web/extension.js b/web/extension.js index 2e421ff..a14dfbe 100644 --- a/web/extension.js +++ b/web/extension.js @@ -665,6 +665,14 @@ function closePhotopeaMaskModal() { const nodeImageData = new Map(); // 存储每个节点的图像和选区数据 +// 检查选区是否包含蒙版边界框 +function regionContainsMask(region, mask) { + return region.x <= mask.x && + region.y <= mask.y && + region.x + region.width >= mask.x + mask.width && + region.y + region.height >= mask.y + mask.height; +} + // 约束选区:框住蒙版且不超出图像边界 function constrainRegion(data, padding) { if (!data.hasMask || !data.maskBounds) return; @@ -773,11 +781,31 @@ async function loadImageAndDetectMask(node, imageName) { if (hasMask && minX <= maxX && minY <= maxY) { data.maskBounds = { x: minX, y: minY, width: maxX - minX + 1, height: maxY - minY + 1 }; - // 使用统一的约束函数计算选区 - constrainRegion(data, padding); - - // 同步到 region_coords widget + + // 优先恢复用户手动调整过的选区(如果仍然包含当前蒙版) + // 避免图像重新加载、关闭编辑器窗口等操作把手动调整重置回 padding 计算值 const coordsWidget = node.widgets?.find(w => w.name === "region_coords"); + let restored = false; + if (coordsWidget && coordsWidget.value && coordsWidget.value !== "{}") { + try { + const saved = JSON.parse(coordsWidget.value); + if (saved && saved.width > 0 && saved.height > 0 && + regionContainsMask(saved, data.maskBounds)) { + data.regionX = saved.x; + data.regionY = saved.y; + data.regionWidth = saved.width; + data.regionHeight = saved.height; + restored = true; + } + } catch (e) {} + } + + if (!restored) { + // 使用统一的约束函数计算选区 + constrainRegion(data, padding); + } + + // 同步到 region_coords widget if (coordsWidget) { coordsWidget.value = JSON.stringify({ x: Math.round(data.regionX), @@ -1339,6 +1367,16 @@ app.registerExtension({ if (data && data.hasMask && data.maskBounds) { // 使用统一的约束函数重新计算选区 constrainRegion(data, v); + // 同步到 region_coords widget,保证显示与后端使用一致 + const coordsWidget = node.widgets?.find(w => w.name === "region_coords"); + if (coordsWidget) { + coordsWidget.value = JSON.stringify({ + x: Math.round(data.regionX), + y: Math.round(data.regionY), + width: Math.round(data.regionWidth), + height: Math.round(data.regionHeight) + }); + } } node.setDirtyCanvas(true); };