〽️ ImageOps Paint
Paint inside ComfyUI — brush, eraser, and pen pressure without leaving the graph
- bg_color
- brush_color
- image
- image
- source_image
- mask
ImageOps Paint is the node that turns a ComfyUI canvas into a place you can actually draw. Brush, eraser, adjustable size and opacity, pen pressure support, and a live preview you paint on directly - the strokes land in the graph, not in a separate editor you have to bounce back and forth from. It's one of the more ambitious nodes in the ComfyUI-Majoor-ImageOps pack, and one people specifically called out as useful when the pack launched.
How it works
You get a canvas (width × height, 64–4096, with sync_dimensions to keep the ratio) and a bg_color background. Optional image input gives you a base to paint over - skip it and you get a blank canvas. The tool dropdown switches between brush and eraser, with brush_color, brush_size (1–256), brush_opacity (0–1), and a brush_edge of hard or soft (with brush_softness controlling the falloff of the soft brush).
The pen-dynamics section is where this stops feeling like a toy: brush_pressure_size and brush_pressure_opacity modulate the brush from your stylus pressure, and brush_tilt_size lets pen tilt widen the footprint. With a tablet that's an actual painting experience, not a circle stamp.
The payload mechanism (worth understanding)
The strokes are transferred to the node as an encoded overlay: overlay_format (WebP or PNG) + overlay_data, a base64 payload the preview sends. The node also accepts an overlay_layers JSON list for compositing multiple overlay payloads, each with its own opacity and enabled state - that's the non-destructive multi-layer painting path. You don't need to hand-edit any of this; the preview UI manages it. But it explains how your strokes survive the round-trip through the graph, and it's why the node still accepts older full-frame base64 PNGs from saved workflows.
Outputs
Three of them: image (the painted result), source_image (the original base, untouched - handy for comparing or masking), and mask (the paint strokes as a matte, which is a genuinely useful byproduct if you want to feed "where I painted" into something else).
Install
ComfyUI Manager (search "ComfyUI-Majoor-ImageOps"), or:
cd ComfyUI/custom_nodes
git clone https://github.com/MajoorWaldi/ComfyUI-Majoor-ImageOps
Restart and hard-refresh the browser (Ctrl+F5 / Cmd+Shift+R). No models; the node uses Pillow for overlay decoding, which ships with ComfyUI.
Where it gets fiddly
The overlay round-trip is the main source of confusion: if strokes vanish after you reconnect something or reload, check that overlay_data is populated - a blank payload means the preview didn't send strokes, which usually means the frontend needs that hard refresh. Second, painting on a huge 4096 canvas with a soft brush can get sluggish in preview; work smaller and upscale if you need the resolution. And remember the mask output is your friend - if you're painting a mask region rather than pixels, grab it and feed it to whatever needs a matte.
Inputs (19)
| Name | Type | Default | Description |
|---|---|---|---|
| bypass | BOOLEAN | false | — |
| width | INT | 102464–4096 | — |
| height | INT | 102464–4096 | — |
| sync_dimensions | BOOLEAN | true | — |
| bg_color | COLOR | #000000 | — |
| tool | COMBO | brush | 2 options: brush, eraser |
| brush_edge | COMBO | hard | 2 options: hard, soft |
| brush_softness | FLOAT | 0.500–1 | How feathered the soft brush is. 0 = nearly hard, 1 = max falloff. Ignored when brush_edge=hard. |
| brush_color | COLOR | #FFFFFF | — |
| brush_opacity | FLOAT | 1.000–1 | — |
| brush_size | INT | 101–256 | — |
| brush_pressure_size | BOOLEAN | true | — |
| brush_pressure_opacity | BOOLEAN | true | — |
| brush_tilt_size | BOOLEAN | false | — |
| overlay_format | COMBO | png | 2 options: png, webp |
| overlay_data | STRING | — | |
| overlay_layers | STRING | — | |
| invert_mask | BOOLEAN | false | — |
| imageopt | IMAGE,VIDEO | Images/Video input. Accepts IMAGE batches and VIDEO frame sources. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| source_image | IMAGE | — |
| mask | MASK | — |