ComfyUI Node

Roto Mask

Keyframe-animated masks inside ComfyUI, no After Effects required

By TechnicalTod·Created 8 months ago·Updated 8 months ago· 0
Roto Mask
    • Mask as image
    • Mask
    • Masked Image
    • Loaded Image
    • Mask Data
    curve_json{"meta":{"version":1,"width":512,"height":512},"shapes":{}}
    frame_index1
    frame_count1

    Every video workflow eventually hits the same wall: you need a mask that moves. You want to replace a background behind a walking person, keep an animated inpaint locked to one region, or protect a face across frames - and every automatic tool (SAM2, segment-anything) either drifts on tracking or grabs more than you asked. Roto Mask is the manual answer: a polygon rotoscoping editor that lives inside the node, with per-curve keyframes and automatic interpolation between them. Draw the shape on one frame, nudge it on the next, and the frames between fill themselves in.

    The name isn't a lie. This is genuine rotoscoping - the old-school animator's technique - brought into the node graph, and it needs no API, no key, and no model downloads.

    How it works

    Roto Mask is a hybrid: a JavaScript canvas editor (the web/ extension) for drawing, and a pure-Python backend that turns your curves into masks. When you drop the node in, the curve_json, frame_index, and frame_count widgets get hidden and replaced by a toolbar with Draw (B) and Edit (V) modes. You click to lay down polygon points, toggle individual points between smooth (curved) and hard (angular), and the shape closes itself into a fill.

    Everything you draw is serialized into a JSON string and stored in the curve_json widget - that's how the roto data survives in your workflow file. On execution the backend validates the schema, then for each curve finds its keyframes on either side of the current frame and interpolates them. Smooth segments are sampled with Catmull-Rom-style interpolation and rasterized with OpenCV, then a Gaussian blur (your feather amount) softens the edge. Each curve keeps its own keyframe timeline, so curve A can have keyframes at frames 1, 10, 20 while curve B has completely different ones, and they interpolate independently.

    The whole thing runs on CPU with numpy/OpenCV. No VRAM, no inference. For batch output you get one mask tensor per frame, ready to feed a video pipeline.

    The inputs and outputs that matter

    You'll barely touch the widgets - the canvas editor manages them. But knowing what they do saves you confusion:

    • curve_json - the whole roto project as JSON (geometry, keyframes, feather, background color, frame paths). Set automatically by the editor.
    • frame_index - the frame to render in single-frame preview.
    • frame_count - how many frames to render as a batch (default 1).

    Outputs, in the order you'll care about them:

    • Mask - single-channel MASK tensor, the one you wire into a VAE Encode for inpainting, a compositor, or anything else that eats a mask. This is your main output.
    • Mask as image - the same mask as an RGB IMAGE for preview or saving.
    • Masked Image - original frames with the mask overlay baked in.
    • Loaded Image - the raw uploaded frames, handy for keeping the clip attached to the graph.
    • Mask Data - the curve JSON passed straight through, useful for caching or inspecting what's stored.

    Installing it

    Easiest path is ComfyUI Manager - search "RotoMask" and install. Manual:

    cd ComfyUI/custom_nodes
    git clone https://github.com/TechnicalTod/ComfyUI-RotoMask
    cd ComfyUI-RotoMask
    pip install -r requirements.txt
    

    Then restart ComfyUI. Dependencies are light - numpy, opencv-python, torch, Pillow - and there are no model files to download, which makes this one of the rarer ComfyUI packs: it works fully offline and won't fight your other nodes over VRAM.

    Gotchas worth knowing

    The README tells you to clone into ComfyUI/custom_nodes/ComfyUI_Roto/. Don't follow that folder name literally - the pack registers as custom_nodes.ComfyUI-RotoMask, so let the clone keep its own directory name or ComfyUI won't find it.

    Bigger one, straight from the backend source: batch rendering always starts at frame 1. When frame_count > 1, the node hardcodes frame_index = 1, so the UI's "start frame" only truly works in single-frame mode. If you set a render range starting at frame 5, expect frames 1–N to come out anyway. Render from frame 1 or do single frames.

    Also: the Loaded Image and Masked Image outputs need the actual frame files still on disk in ComfyUI's input folder (their paths are embedded in the JSON). If you move or share a workflow, those two outputs go black - but the Mask output still renders fine, because the geometry doesn't need the images. Which is also your workflow-sharing tip: the roto data travels, the video doesn't. And since all that keyframe data lives in curve_json, a long multi-curve project will visibly bloat your workflow JSON. It's the price of not needing a separate project file, and for a tool this niche, it's a fair one.

    Categorymask

    Inputs (3)

    NameTypeDefaultDescription
    curve_jsonSTRING{"meta":{"version":1,"width":512,"height":512},"shapes":{}}
    frame_indexINT11–9999
    frame_countoptINT11–9999

    Outputs (5)

    NameTypeDescription
    Mask as imageIMAGE
    MaskMASK
    Masked ImageIMAGE
    Loaded ImageIMAGE
    Mask DataSTRING