Load Image (In-Memory)
Load an image and paint its mask without ever touching your hard drive
- image
- mask
The normal way to set up a ComfyUI inpaint is two separate steps: drop your image through LoadImage (which copies it into input/), then open the built-in image editor to paint a mask (which writes that to input/clipspace/). Two nodes, two trips to disk, and a third thing to go wrong when the clipspace file goes missing. Load Image (In-Memory) collapses both into one node: drag an image straight onto it, paint the mask on the same widget, and the pixels never touch your drive at all.
The whole image and mask get base64-encoded into the node's hidden string widgets and decoded to tensors on the backend. Nothing lands in input/, input/clipspace/, output/, or temp/ - that's the entire pitch of the pack it ships in, and it's also the mechanism, because the image data is the node state.
How it works
The frontend part is the interesting half. The node's JS registers a DOM widget on LoadImageInMemory: drag an image onto its canvas (or hit the upload button, or double-click), and a FileReader + <canvas> converts it to a base64 PNG string that gets stuffed into the image widget. Below it sits a small toolbar - brush, eraser, a 4–150px brush-size slider, and a reset button - and painting writes a second base64 PNG into the mask widget. The node resizes freely, like the FastPreview node, and the canvas redraws at your device's pixel ratio so it stays crisp on HiDPI screens. It's a genuinely nice little editor, the kind of thing that makes you wonder why the built-in one is still a separate modal.
The Python side is deliberately boring: load() base64-decodes the image, converts it to RGB, and turns it into a [1, H, W, 3] float tensor scaled to 0–1. The mask becomes a [1, H, W] grayscale tensor, resized to the image if the sizes ever disagree, and it defaults to all-zeros if you loaded a plain image without painting. An IS_CHANGED hash of the two strings tells ComfyUI to re-run whenever you paint another stroke, so mask edits propagate without hunting for a refresh button.
The inputs and outputs that matter
Here's the part that trips people up: image (required) and mask (optional) are string widgets, not sockets. You can't wire anything into them - they're hidden text fields the frontend fills for you. The real connections are the two outputs:
image(IMAGE) - your dropped image as a tensor. FeedsVAEEncode,InpaintModelConditioning, or straight into aLoad Image-style consumer.mask(MASK) - what you painted. Wires intoSetLatentNoiseMask, an inpaint crop node, or any detailer that takes a MASK.
That's the whole input surface. Drop an image, paint, wire the two outputs into your inpaint chain.
Installing it
It's a ComfyUI Manager one-click:
- Manager → Custom Nodes Manager → search "comfyui-in-memory-nodes" → Install, then restart ComfyUI (and refresh the browser tab - the DOM widget lives in frontend JS).
- Or the manual route:
cd ComfyUI/custom_nodes
git clone https://github.com/SujithSiddireddy/comfyui-in-memory-nodes.git
Then restart ComfyUI. No extra Python dependencies, no model downloads, MIT licensed. The only "dependency" is a recent browser - everything client-side.
Where people get burned
- Running it empty. The backend raises
no image was provided (drag an image onto the node)if you queue it with nothing loaded. Drop the image before you hit run. - Workflow bloat. Because the image rides in the workflow JSON as base64, your saved
.jsongrows to roughly 4/3 the image size - and it carries the mask too. That's the same trade the "self-contained" convenience buys you: a shared workflow embeds its own input, no missinginput/file, but a stack of 4K images makes for chunky files. - It's a UI node. Everything happens in the browser frontend, so it's not a natural fit for pure API/headless calls. Use it interactively.
- It's young. This pack is tiny and new - don't expect a long track record. For a plain image load with no mask, core
LoadImageis still fine; the in-memory node earns its place when you want drag-and-drop and a mask in one self-contained step.
The mask still does what a mask uniquely does - bit-identical pixels outside it, a denoise dial inside it - and this node just makes that part of the flow less annoying. Worth a try for your next inpaint.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image | STRING | — | |
| maskopt | STRING | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |