Pixel Lock Composite
Stop Qwen-Edit From Wrecking the Rest of Your Image — Paste Back Only What Changed
- original
- edited
- mask
- image
- change_mask
Qwen-Image-Edit is a brilliant liar. Ask it to "change the shirt to red" and it cheerfully redraws the whole frame - the face drifts, the background shifts, the lighting shifts a little - because that's the architecture: it re-emits the image from scratch, and nothing pins the pixels you didn't ask about. Do five rounds of edits and the degradation compounds into mush. This node is the fix that needs no mask: it figures out which region the edit actually changed, then composites only that back onto your untouched original, at original resolution.
It's not a model, not a LoRA, not a ControlNet. It's a couple of torch tensor ops in a box, and that's the whole point - zero dependencies, installs in five seconds, costs you nothing at runtime. Think of it as the lazy man's Inpaint Crop and Stitch: the same "only paste back the changed bits" idea you'd otherwise build with ImageCompositeMasked, except it auto-detects the changed region instead of making you paint a mask.
How it works
You feed it original and edited images (the ComfyUI outputs of your VAEDecode after Qwen-Edit). Internally it:
- Resizes the edited image to the original's size (the model's ~1MP output gets scaled back up to match).
- Computes the per-pixel absolute difference between the two, averaged across color channels.
- Blurs that difference map with a gaussian (
blur), so one-off noise doesn't read as "changed". - Thresholds it (
threshold/255) into a binary mask of what changed. - Grows the mask with max-pool dilation (
dilate) so it fully covers the edit, then feathers the edges (feather). - Blends
original * (1-alpha) + edited * alpha.
Where the mask is 1, you get Qwen's edit. Where it's 0, you get your original, byte-for-byte. The whole thing is tensor-native, so it never leaves the GPU.
The inputs that matter
- original / edited - the two images. Everything else has defaults that work.
- threshold (default 16) - how big a pixel difference counts as "changed", on a 0–255 scale. Lower catches subtler edits. If a small tweak is getting ignored, drop it.
- dilate (default 6) - grows the mask so it fully covers the edit. Raise it if you see a hard cut-off ring around the changed area.
- feather (default 4) - softens the seam so the pasted patch doesn't show an edge.
- bypass (default false) - pass the raw edit through untouched. This is where you wire in the pack's other node, Global Edit Guard, for global/style edits.
- mask (optional) - supply your own edit mask and it skips auto-detect entirely.
It outputs image (the locked composite, ready for Save Image) and change_mask - a MASK you can preview to see exactly what it thought changed, which is the best debugging tool in the pack.
Install
Search PixelLock in ComfyUI-Manager, or:
cd ComfyUI/custom_nodes
git clone https://github.com/GritAI-Labs/ComfyUI-PixelLock
Restart ComfyUI. No dependencies, no model downloads - the pack's pyproject lists an empty dependency array and the code only imports torch, which ComfyUI already ships.
Where people get burned
The name's a promise the node can't keep on its own. Global/style edits will get reverted. An oil-painting or "make it night" edit changes the whole look with a small pixel difference, so the auto-mask barely covers anything and the composite pastes back mostly your original. The pack's own 374-edit sweep found ~8% of global edits wrongly erased this way. That's exactly what Global Edit Guard exists for - wire its bypass output into this node's bypass input and the lock steps aside for those.
Also keep expectations straight: inside the changed region you get whatever Qwen produced. This node guarantees the untouched pixels stay identical, not that the edited region is good - if the model mangled the face, that's in your output and no composite fixes it. And unlike crop-and-stitch, it doesn't buy you extra generation budget on small regions; it operates at the original's resolution, so a tiny face edit doesn't get a bigger canvas.
The validation story is genuinely good though: 107/107 local edits on people applied correctly across every framing, and five rounds of edits held the unedited pixels at zero drift while an un-locked chain degraded each round. For multi-round character work, it's the difference between a workflow that rots and one that doesn't.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| original | IMAGE | — | |
| edited | IMAGE | — | |
| threshold | INT | 160–255 | 0-255 pixel-diff to count as 'changed'. Lower = catches subtler edits. |
| blur | INT | 30–64 | Denoise the change map before thresholding. |
| dilate | INT | 60–128 | Grow the mask to fully cover the edit. |
| feather | INT | 40–64 | Soften the seam. |
| bypass | BOOLEAN | false | True = pass the raw edit through (for global/style edits). Wire GlobalEditGuard here. |
| maskopt | MASK | Optional explicit edit mask; overrides the auto change-detect. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| change_mask | MASK | — |