Mask External Edit
GPT Image fixes straight from the ComfyUI canvas
- image
- mask
- image
- debug_crop
- edited_crop
- used_mask
- status
Every local fix runs the same loop: crop the broken region, re-render it, paste it back with a feathered seam. Mask External Edit runs that loop for you and then hands the middle step to a cloud image-editing API - OpenAI's GPT Image, OpenWond, or any HTTP endpoint you point it at - instead of burning a diffusion pass on your GPU. You paint a mask, it crops with context padding, ships the crop off, and blends the edited result back into the original so well the seam usually vanishes. If you've ever wanted GPT Image's masked edits inside a ComfyUI workflow, this is the least painful way to get them.
The name isn't lying: this is an API-wrapper node, not a local model. GPT Image has no open weights you can download, so calling the cloud is the only door - or you have the workflow but no spare VRAM. It's the same honest trade every API node makes: each call costs money, and your crop - not your whole image, just the crop and mask - leaves your machine. It's the right tool when you can't run the model locally, not a default to build every workflow on.
How it works
Read the source and the loop is clean. The node takes your image and painted mask, autocontrasts the mask, and figures out the painted region with mask_mode. The auto default is worth knowing: ComfyUI MaskEditor/clipspace masks often store painted areas as darker alpha over an opaque image, so the node inverts when the background dominates. If the edit comes out backwards, flip white_edits / black_edits.
Then it grows the mask (mask_grow), finds the painted bbox above threshold, pads it with padding pixels of context, crops, and scales down to crop_max_size (default 1024) so you're not sending a 4K patch to a metered API. The provider call happens - OpenAI gets the crop plus an RGBA mask where transparent = edit area; OpenWond gets two reference images (crop + a black/white mask where white = edit); custom_http gets a multipart/form-data POST to whatever URL you set in api_endpoint, with image, mask, prompt, and task fields. Finally it pastes back: the edited crop is resized to the crop bbox, the mask gaussian-blurred by feather, and color_match (the default blend) matches each channel's mean/std to the original region so the lighting doesn't scream.
The inputs that actually matter
For a first run you really set these:
image+mask- the source and the area you want edited.provider-openai(default),openwond_draw,custom_http, ordebug_echo.task+prompt-general_fix,fix_hands,enhance_face, orchange_expression, plus your instruction. The node folds the task into the prompt sent to the API.api_key/api_key_env- paste a key, or setMASK_EXTERNAL_EDIT_API_KEYas an env var and leave the field empty. Prefer the env var: keys baked into node fields end up in exported workflows and screenshots.padding/mask_grow/feather- your seam quality knobs. The README's starting points: hands like 160–256 padding / 8–20 grow / 20–48 feather; a face can get away with less.
The rest has sane defaults. openai_model defaults to gpt-image-2, timeout_seconds to 120, on_error to raise.
What comes out
Five outputs: image (the final pasted result), debug_crop and edited_crop (what went out and what came back), used_mask (the paste mask used), and status (a string that tells you what happened or why it failed). Wire image where your sampler output would go; keep status on a text preview while debugging.
Install
There's no model to fetch here - requirements.txt is just requests and Pillow:
cd ComfyUI/custom_nodes
git clone https://github.com/wuhu290/ComfyUI-Mask-External-Edit.git
cd ComfyUI-Mask-External-Edit
pip install -r requirements.txt
Restart ComfyUI and search "Mask External Edit" on the canvas. It's new enough that ComfyUI Manager may not list it yet - use Manager → Install via Git URL and paste the repo URL.
Where people get burned
- GPT Image masks are prompt-guided, not pixel-perfect - OpenAI is upfront that the edited region may not follow your mask precisely. The node checks for this: if the API returns an unchanged crop, it raises rather than quietly pretending to have fixed your hand.
- Inverted edits with MaskEditor/clipspace masks - switch
mask_modeoffautoas described above. - OpenWond returning
413(request too large) - dropopenwond_max_sidefrom 768 to 512. - Silent failure - if you set
on_error: return_original, a failed call hands you the original image and only a string instatussays why. Keepraisewhile testing, or you'll swear the node is broken. - Free rehearsal: run
provider: debug_echofirst. It paints the detected mask area hot pink on the crop with zero API calls, so you can verify crop, mask polarity, and paste logic before spending a cent.
Inputs (20)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| mask | MASK | — | |
| provider | COMBO | openai | 4 options: openai, openwond_draw, custom_http, debug_echo |
| task | COMBO | general_fix | 4 options: general_fix, fix_hands, enhance_face, change_expression |
| prompt | STRING | Fix only the masked area. Preserve the original character, pose, style, lighting, and background. | — |
| api_endpoint | STRING | — | |
| api_key_env | STRING | MASK_EXTERNAL_EDIT_API_KEY | — |
| padding | INT | 1600–1024 | — |
| mask_grow | INT | 120–256 | — |
| feather | INT | 240–256 | — |
| crop_max_size | INT | 1024256–4096 | — |
| threshold | INT | 161–255 | — |
| timeout_seconds | INT | 12010–600 | — |
| blend_mode | COMBO | color_match | 2 options: normal, color_match |
| api_key | STRING | — | |
| openai_model | STRING | gpt-image-2 | — |
| mask_mode | COMBO | auto | 3 options: auto, white_edits, black_edits |
| on_error | COMBO | raise | 2 options: raise, return_original |
| openwond_resolution | COMBO | 1K | 3 options: 1K, 2K, 4K |
| openwond_max_side | INT | 768256–2048 | — |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| debug_crop | IMAGE | — |
| edited_crop | IMAGE | — |
| used_mask | MASK | — |
| status | STRING | — |