Vector Roto — Bezier (MEC)
Vector rotoscoping with Catmull-Rom tweening, no model needed
- mask
- info
Roto is the old-school way to get a mask: you draw a spline around the thing, keyframe it as the thing moves, and let the software tween between keyframes. It's deterministic, it's controllable, and it works when no segmentation model can figure out what you're pointing at. VectorRotoMEC is the MEC pack's take on that - a cubic-Bezier vector roto engine with Catmull-Rom keyframe tweening that hands evaluated polylines to a scanline rasterizer. No models, no downloads, just math.
This is the node for the fiddly cases: a shadow, a glint, a prop that changes shape - anything where SAM's "3 candidate masks, pick one" flow is the wrong tool and you'd rather draw the shape yourself. It pairs naturally with the pack's SplineMaskMEC, but the division of labor matters: SplineMaskMEC is the interactive canvas node; VectorRotoMEC is the headless rasterizer that takes spline data as JSON and turns it into frames.
How it works
You feed it roto_json - a JSON structure describing a canvas size and a list of keyframe frames, each carrying its own splines. It then evaluates each spline as a cubic Bezier, tweens between your keyframes using Catmull-Rom interpolation, samples each curve at samples_per_seg points, and scanline-rasterizes the resulting polygons into a mask batch. Everything is resolution-independent until the final rasterize, and the canvas coordinates are scaled to your requested width/height.
The default input is a skeleton that shows the expected shape: {"canvas": {"w": 1024, "h": 1024}, "frames": [{"frame": 0, "splines": []}]}. You replace splines with your control points; the interactive Spline Mask canvas elsewhere in the pack is the convenient way to author that JSON.
Inputs and outputs that matter
roto_json(STRING, multiline) - the keyframed spline data. This is the input you'll actually edit.frame_count(INT, default 1, up to 4096) - how many frames of mask to produce. Set this to your clip length.width/height(INT, defaults 1024) - output resolution; the canvas JSON is scaled to fit.samples_per_seg(INT, default 24) - curve resolution. Higher is smoother and slower; 24 is usually plenty.feather_px(FLOAT, default 0) - edge softness in pixels, for a softer mask edge.
Outputs:
mask(MASK) - the rasterized result,(frame_count, H, W).info(STRING) - diagnostics (frame count, spline count, timing).
Where it fits
Classic use: roto a static-ish element across a short clip - draw it on keyframes, let the Catmull-Rom tween fill the gaps, rasterize to a mask batch, and feed that into whatever consumes masks (inpainting, compositing, the pack's mask propagate nodes). It's CPU-only, so it runs anywhere.
Installing it
Standard pack install:
cd ComfyUI/custom_nodes
git clone https://github.com/Code2Collapse/ComfyUI-CustomNodePacks.git
restart ComfyUI or use Manager → "CustomNodePacks", and look for [MEC] Loading MaskEditControl node pack … in the console. No extra dependencies for this node.
The honest caveat
Roto is tedious no matter what, and this node doesn't do motion tracking - the tweening is geometric, not driven by the footage. For footage where the subject actually moves, pair it with a tracker or the pack's spline track mode (Lucas-Kanade) instead of hand-keyframing every move. For mostly-static shots, this is a clean, model-free way to get a precise mask out of a JSON description.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| roto_json | STRING | {"canvas": {"w": 1024, "h": 1024}, "frames": [{"frame": 0, "splines": []}]} | — |
| frame_count | INT | 11–4096 | — |
| width | INT | 102416–8192 | — |
| height | INT | 102416–8192 | — |
| samples_per_seg | INT | 242–256 | — |
| feather_px | FLOAT | 0.00–64 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |
| info | STRING | — |