- Words light up as you type (@1, times, cuts, quotes, labels), with @
autocomplete and a ? cheat sheet
- Timeline bar shows every picture a line names; drag a block to reorder,
drag its edge to set its time, click an edge for a cut, click a block to
jump to its line
- Sound and Music boxes (new optional inputs, appended) fill H3's
overall_soundscape and non_diegetic_music
- Pictures and video as chips with their jobs; Length, Camera and Look stay
plain rows so they can still be wired
- Help page and changelog updated; tests for the colors, autocomplete,
timeline edits and the new inputs
Image Crop + Rotate + Pad's stitcher now remembers the picture and the
crop's position. With Paste onto on original, Stitch Inpaint lays the
result on the whole picture, straight, resized or turned. The default
stays crop, so saved workflows run as before.
Watch the picture form one sampling step at a time and stop a run that is
going wrong. Step Preview needs no wires and shows whichever sampler is
working, with Stop and Stop all. Save Image and Save Video get a steps
switch, off by default, that shows the steps where the saved picture goes,
with a Stop button.
A queued workflow that shows its steps asks ComfyUI for the auto preview
method for that run, only when no method was chosen in the frontend or on
the command line.
The node gets a second output, image_b: the B batch, untouched, the
way image_a already passes through. image_a stays the first output, so
saved workflows keep their wires. The slider, its resting side, Erase
and Save are unchanged.
Help page, tooltips, README line and CHANGELOG say so; the node API
snapshot gains the one new output.
The mask is white inside every box and black elsewhere, at the size of the
picture on background (or the canvas without one). Draw a box over the words
on a sign, wire the mask into an inpaint chain, and only the words change.
It comes after the other three outputs, so saved workflows keep their wires.
Draw boxes on a canvas and say what goes in each one: a thing, or words to
write in the picture. Wire elements to MOSS-Image Layout Prompt and the
picture puts each thing where its box is.
It gives what ComfyUI's own Create Bounding Boxes gives (preview, bboxes,
elements; same types, same order, same values from the same boxes), so it
can take that node's place, and its canvas also draws in the classic node
look. The boxes are kept as parts of the canvas in a hidden socketless
string, so a wired size never moves them.
The four mode buttons become one switch, Slide | A | B | Erase, whose
marker slides to the choice. In Erase the brush's two jobs and the
picture on top are switches of the same kind, and PSD is a plain
off | on.
Save moves to the bottom right of the node, larger, with a disk icon,
in a row of its own with the name of the files and the PSD choice.
The files can be named. A Name field on the node takes letters,
digits, spaces, dots, dashes and underscores; the server checks the
same rule and refuses anything else, so a folder is never part of a
name. A new optional filename input takes a wired name, such as the
source picture's file name: its folder and extension are dropped. The
save route still names no file to read.
Size and Soft scrub from their label and from the unit inside the box
as well as from the number (the scrub control gains handle() for this).
Workflows saved before load as they were: 60 that use the node were
opened (the pack's examples, published workflows, the oldest saved
shapes), 72 nodes kept their size, mode and links. The node is one
input socket taller.
A new node in the Mask group. The picture is on the node: moving over it
lights up what a click would take and names it, a click picks it, and two
arrows swap the pick for the next thing at that spot. Subject and
Background take the picture's main subject, or everything else, in one
click. Pick | Draw switches to a brush for an area painted by hand, + and -
add or take away, Undo takes a pick back, and every pick has a chip under
the picture. Fine edges follows hair and fur strand by strand, takes out
background seen between the strands, and gives a cut-out the thing's own
colour. The eye beside Undo and Clear shows the result as a cut-out or as
the mask. Outputs: the mask, the cut-out and the names of what was picked.
The search is ComfyUI's own SAM 3. Subject, Background and Fine edges use
ComfyUI's own background-removal model (ComfyUI 0.21 or newer). The pack
downloads nothing.
With it: a widget card can name its choices with a function and keeps
showing a value its list does not offer, the help page, an example
workflow with its thumbnail, the README section and the CHANGELOG entry.
A part painted in the MaskEditor (see-through in the saved file) was
always handed on as flat fill. The new optional painted_area input,
"fill" by default, can be set to "keep picture": the mask is unchanged
and the image, original and prompt_image outputs carry the stored
picture there. The editor's Inpaint & Stitch section has the switch.
Two Image nodes that take a picture past the size a model can redraw in
one go. Tiled Upscale sets the finished size in megapixels and cuts the
picture into tiles; Tiled Upscale Stitch blends the joins, keeps each
tile's colors on the picture's own and fits the result to the size asked
for. Any model can sit between the two.
Adds the tile planner with its tests, the two widget cards, help pages,
README sections, a changelog entry, the API snapshot and a new example,
Qwen Image 2.1 Upscale + Restore. The README's example count now matches
the folder.
A new Utility node for explaining a workflow on the canvas. The text grows
to the largest size that fits the note, so it reads without zooming in, and
each point gets a thin teal line beside it.
An arrow emoji in the text (or the arrow row under the editor) becomes a
teal arrow in the line. Click a node while editing, then press an arrow, and
the arrow is linked to that node: clicking it later brings the node into
view, the way Workflow Switches does. The link is kept in the text as #id,
so editing the words never moves it.
The node has one hidden text input and no outputs, so it never runs.
A loader for many pictures. The card chooses the folder inside ComfyUI's
input or output folder, and a thumbnail gallery under it ticks the pictures
to use. All in one run sends every picked picture through as a list; one
per run loads one picture each run and moves on when a run is queued. It
gives image, mask, filename, index and count. Pictures dropped on the node
or added with Add are uploaded into the input folder.
Folder names and picked names are attacker-controlled: a folder is only
read as a path below the input or output folder, links that lead out are
refused, and picked names are compared with what the folder holds, never
opened.
Fill holes fills gaps that lie fully inside the mask, with the same fill
Mask Refine uses. Free VRAM takes SAM 3 and its text reader out of the
graphics card's memory right after the search, with ComfyUI's own unload
of one model, also when the search found nothing. Both are off by
default.
Registers the node, gives it a widget card and the shared preview panel
with a FIND tool that runs the node alone, a readout of how many things
matched and how sure SAM 3 was, and the stop message on the node's own
face. A stopped run shows the plain picture, not the last find. Each name
asks SAM 3 for up to 16 matches, and Sureness starts at 0.7.
A see-through part of a picture - a cutout's missing background, an oval
photo's corners, a hole - kept its stored colour blended over the fill.
That colour is often black or the old background, so the model got a
dark or light ring round the picture and Stitch Inpaint pasted part of it
back. A pixel less than 90% as solid as the picture's most solid pixel is
now empty, like a corner a turn leaves: filled with the fill colour,
marked in the mask, new area for both seams. Kept pixels are fully solid
in their own colour.
A new last output, prompt_image, is the image with see-through parts on
white instead of the fill, for the node that writes the prompt: shown the
gray fill, a prompt writer describes a gray backdrop and the model keeps
it. original shows see-through parts on white instead of black.
A picture at least 90% solid everywhere goes through exactly as before;
the new tests hold every output and both seams to the old transform.
Paint everything except a person or a pet, turn on fill holes, and it
filled the unpainted subject too, so the part meant to stay was repainted.
The new optional max_hole_size input (a percent of the picture, 0 to 100)
sets the biggest hole fill holes fills. The default, 0, fills every hole
exactly as before, so saved workflows and API prompts run the same; at 2
the gaps a quick brush leaves still fill and a subject left unpainted
stays. Areas touching the picture's edge are never holes, as before.
- Hole sizes come from scipy's labels, the same 4-connected holes its
fill finds; the torch fallback labels the same 8-connected holes as its
own fill. Each picture in a batch is measured on its own, after expand.
- The card gets a Max hole size row with a % unit, dimmed while Fill holes
is off, and a resetUnknown fallback of 0 for 2.x saves that hold the
card's empty value in its slot.
- Tests: a hole above the limit stays, holes up to it fill, 0 matches the
old fill, edge-touching areas never fill, the torch fallback agrees,
batches, and older saves opening with 0.
- Node API snapshot, help page and changelog.
A dark or colourful picture padded with the #808080 gray came back from
Krea 2 AnyPaint with the gray still there: a gray frame around the picture,
or gray corners on a turned one. AnyPaint was trained with the new area of
its reference filled with the median colour of the picture, so a gray patch
next to a dark photo reads to it as part of the scene and it paints it back.
It did the same with every Krea 2 model file tried.
Krea 2 Encode gets an optional mask input, appended last. When it is wired,
the new area of the reference (mask above 0.5) is refilled with the median
colour of the rest before it reaches the VAE and the vision tower. The
canvas the rest of the graph uses keeps its gray. Without the link nothing
changes.
Seam is a new optional choice, appended after color_match. "classic"
is the default and the old stitch, bit for bit. "blend in" hands the
model's picture over to the source by depth into the picture: colour
and tone change over slowly, fine detail changes over in a short ramp
where the two line up, the new area keeps the model's pixels, and the
deep picture keeps its own. There is no global tone shift, so
color_match and fix_edge_halo do not apply and the card hides them.
The depth map comes from the generated-area mask of a Crop + Rotate
+ Pad stitcher, or from the source rectangle of a Load Image + Pad
stitcher, with sides flush with the frame left out. It is computed
once per stitcher and shared by every frame, with scipy when present
and an exact torch fallback otherwise. Crop For Inpaint stitchers
keep the classic stitch. With blend in, blend_mask is the hand-over
weight.
An older workflow can carry the card's empty value in the slot Seam
now takes; the card resets it to classic on load and the node accepts
it as classic. Classic output is pinned against the previous code for
Load Image + Pad, Crop + Rotate + Pad, Crop For Inpaint and clip
stitchers.
When the grown context box was bigger than the picture on an axis (a wide
or tall mask, or a small picture), fit_rect centred it and the overflow was
replicate-padded: the sampler got stretched copies of the edge pixels, and
a megapixel target spent its budget on them. On a 1184x2112 portrait with
the coat painted, only 42% of the canvas was the picture and the new part
was drawn at 0.52x.
New optional input keep_inside (card row "Stay in picture", on by
default) clamps the box to the picture on the overflowing axis before it
is fitted, so the crop holds only real pixels (0.80x on that portrait).
The helper keeps the old padding by default for existing callers; off on
the node is the old crop. Appended as the last optional input, so saved
workflows keep their values; ones whose box ran past the picture now
render a little differently.
An edit made at the picture's own size loses whatever the model pushed
past the canvas edge, so realigning it left an empty strip on 13 of 16
held-out edits. Realign to Source now takes an optional stitcher: given
Load Image + Pad's stitcher for the padded canvas the edit was made on,
it measures inside the picture's area only and returns just that area,
so what the model drew into the margin fills the edges.
- A drift within half a pixel of a whole-pixel shift is cut straight
out of the edit with no resampling; an edit that already lines up
comes back untouched.
- The coarse pass also tries the edit at 0.86x-1.28x, so a large zoom
is found; models often reframe a padded canvas by zooming in to fill
it, and that zoom does not count toward Max zoom.
- The report says whether the frame was cut out or warped and names the
margin each side needed when an edit ran out of picture.
Load Image + Pad gains a mirror fill, which models read as more of the
scene: over 16 edits it provoked a reframe on 2, against 7 for edge
pixels and 5 for flat gray. The Qwen Image 2.1 Edit + Realign example
now edits on a 64 px mirror margin with the stitcher wired.
Qwen Image 2.1 draws broad edits (watercolor, anime, oil) a few percent
taller than the source, up to about 12%, differently for every seed, and
snapping sizes does not change it. Realign to Source measures that zoom
and shift against the source and warps the edit back onto the source's
frame, at the source's size. empty_mask marks the strip the model pushed
out of view; report gives the measured zoom, shift and worst corner, or
why a frame was left as it is. The display name carries EXPERIMENTAL.
Promoted from the lab node. The measurement is unchanged (the same
matrices on 166 real Qwen 2.1 renders). The warp now samples the edit
with its edge pixels extended for every fill, so the gray and source
fills no longer draw a dark line beside the strip.
New example: Qwen Image 2.1 Edit + Realign, the Qwen 2.1 Edit graph with
the realign step after decode, its report, and before and after compares.
An optional source_image socket pads an image from another node instead
of the chosen file, so a step before the pad (flattening a transparent
picture onto white, say) no longer has to be saved and loaded back.
- The wire wins over the file; validation skips the file check while it
is connected and runs core's link-type check itself, since asking for
input_types turns core's off for the node.
- A wired run hands the canvas a small temp preview (one file per node,
overwritten each run) and the true source size, so the stage draws the
last image it padded; before the first run it shows an outline.
- Card rows gain linkedBy, so the Source row dims while a wire replaces it.
- Appended and optional with no widget: saved workflows and API prompts
load unchanged. Node API snapshot refreshed.
Resolved:
- node_image_crop_rotate_pad.py: optional inputs are the four resize
inputs, then stitch_blend, then stitch_grow; node_api.json regenerated
with test_node_api.py --update (no breaking change against main).
- test_transform_outputs.py: the optional-inputs test expects the stitch
inputs after the resize ones.
- transform_editor.mjs: the Inpaint & Stitch section and its status line
serve the image node too (feather branch) beside the size-chain status
(this branch); the Cancel code is #73's, line for line.
- LTX example note and the Clip help page carry both branches' wording.
- CHANGELOG keeps every bullet.
- Rotating keeps the crop's size over the same part of the picture from
the knob, slider, number box and Reset rotation (cropForRotation).
- A readout under the picture names every step that sets the output size
(crop -> pad -> align -> resize) and warns when the resize Step
stretches the picture or Align pads fill ahead of a resize; ticking
Resize shows Megapixels and Step on the face.
- Locked-aspect crops use integer maths (_ratio_box / ratioBox), so the
size shown equals the run.
- Image Crop + Rotate + Pad's four resize inputs are optional, so API
prompts saved before them validate again.
- Video Crop + Rotate + Pad -> Clip: one Length switch replaces the Fixed
frames and Limit boxes; OUT sits on the last output frame; a connected
fixed_frames / frame_load_cap takes over the length.
- The stitcher pastes the unfaded canvas when feather > 0
(transform_pil_layers / keep_unfaded). Superseded by mask-only feather
and dropped in the next commit.
Carries a copy of the editor Cancel / discard-prompt change from
feat/editor-cancel-discard (#73), which this work was built on.
The image and video transform nodes faded the picture into the fill
colour across the feather ramp. Outpaint models read that ramp as
content (the Qwen 2.1 outpaint LoRA painted a darker band along a tilted
edge, LTX's IC-LoRA left feathered bars unpainted), and the stitcher
kept the faded pixels, so Stitch's colour match measured its drift
against them and pulled the fill toward grey (d(ab) 18.5 at feather 24 on
a straight pad, 2.6 now). Feather now shapes only the mask and the stitch
blend built from it; the picture keeps a hard edge against the fill, so a
feathered canvas is the feather-0 canvas pixel for pixel.
Image Crop + Rotate + Pad gains the clip node's optional stitch_blend
(32) and stitch_grow (0) inputs, and its editor shows the same Inpaint &
Stitch section with Show blend. The editor's live fade overlay goes with
the fade.
Save Image joins the shared input_preview panel with the PREVIEW switch,
backed by a new optional preview input (display only: the file is saved
and listed in the queue either way). The embed row reads
Embed workflow: no | yes.
Video Crop + Rotate + Pad -> Frame had gained its four resize inputs as
required, so an API prompt saved before them stopped validating. They
are optional now; the widget order is unchanged.
tests/test_node_api.py loads the pack the way ComfyUI does and holds every
node to tests/fixtures/node_api.json: inputs and outputs may only be
appended, never renamed, reordered, removed or newly required. Its rule
tests need only the standard library and run in CI.