Nodes/ComfyUI-Majoor-ImageOps/〽️ ImageOps Paint
ComfyUI Node

〽️ ImageOps Paint

Paint inside ComfyUI — brush, eraser, and pen pressure without leaving the graph

By MajoorWaldi·Created 8 months ago·Updated 8 days ago· 11
〽️ ImageOps Paint
  • bg_color
  • brush_color
  • image
  • image
  • source_image
  • mask
bypassfalse
width1024
height1024
sync_dimensionstrue
toolbrush
brush_edgehard
brush_softness0.50
brush_opacity1.00
brush_size10
brush_pressure_sizetrue
brush_pressure_opacitytrue
brush_tilt_sizefalse
overlay_formatpng
overlay_data
overlay_layers
invert_maskfalse

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.

Categoryimage/imageops

Inputs (19)

NameTypeDefaultDescription
bypassBOOLEANfalse
widthINT102464–4096
heightINT102464–4096
sync_dimensionsBOOLEANtrue
bg_colorCOLOR#000000
toolCOMBObrush2 options: brush, eraser
brush_edgeCOMBOhard2 options: hard, soft
brush_softnessFLOAT0.500–1How feathered the soft brush is. 0 = nearly hard, 1 = max falloff. Ignored when brush_edge=hard.
brush_colorCOLOR#FFFFFF
brush_opacityFLOAT1.000–1
brush_sizeINT101–256
brush_pressure_sizeBOOLEANtrue
brush_pressure_opacityBOOLEANtrue
brush_tilt_sizeBOOLEANfalse
overlay_formatCOMBOpng2 options: png, webp
overlay_dataSTRING
overlay_layersSTRING
invert_maskBOOLEANfalse
imageoptIMAGE,VIDEOImages/Video input. Accepts IMAGE batches and VIDEO frame sources.

Outputs (3)

NameTypeDescription
imageIMAGE
source_imageIMAGE
maskMASK