D2 Random Mask
A rectangle that lands somewhere new every time you queue
- width
- height
- mask
- mask_x
- mask_y
- mask_w
- mask_h
This is a utility node and it makes no apology for it: it emits one rectangular MASK plus the pixel coordinates of that rectangle, and it puts the rectangle somewhere new every run. Nothing here detects anything - for that you want the Impact Pack. This is for when you want a region, randomly placed, and nobody cares which one.
What you'd actually use it for
Three honest cases. First, testing an inpaint chain: paint one mask by hand and you've tested one spot on one image. A random mask runs your pipeline over a different part of the frame every queue, which is how you find the seams and size mismatches a hand-placed box quietly avoids. Second, randomised crop and detail passes - feed mask_x/mask_y/mask_w/mask_h into a crop node and every run pulls a different patch, which is real dataset augmentation and a cheap way to check a checkpoint is consistent across the whole frame. Third, composite smoke tests: if it survives the box landing anywhere, it survives the box landing where you wanted it.
How it works
The node has its own random.Random(seed) instance rather than touching the global random module. That matters: other random pickers in the graph use the global generator, and mutating it drags unrelated nodes' results around with it.
Per run it draws a width and a height independently - inclusive randint between your min and max, clamped to 1…canvas size - then picks a uniform top-left corner from 0…width-w and 0…height-h. Size first, corner constrained to what's left: the rectangle can never poke outside the canvas, it just comes out smaller than asked if your min_width exceeds the frame. The mask itself is a float32 tensor of shape (1, height, width) with the rectangle set to 1.0. Hard edges, no feather - GrowMask or MaskBlur if you need the seam softened (4–12px is the usual range for inpaint seams).
Inputs and outputs
Set these:
width/height- the mask canvas, in steps of 8, default 1024×1024. Match this to your latents, or the mask arrives at a different resolution than the thing you're masking.min_width/max_width/min_height/max_height- the rectangle's pixel dimensions, 1–16384. Defaults are 512/512/512/512, i.e. a fixed 512×512 square wandering the frame. Widen the gap between min and max if you want the size to vary too.seed- it carries thecontrol_after_generatewidget, which is what makes the node re-roll between queues. Set it tofixedto lock a region you liked.
min and max in either direction get swapped rather than rejected, so typing them backwards costs you nothing.
Outputs: width and height (passthroughs of your inputs, handy for sizing a matching latent or an inpaint encode without a second pair of widgets), then the good ones - mask, and mask_x / mask_y / mask_w / mask_h, the pixel rectangle the node actually rolled. Those four are the difference between "a random mask" and "a random region I can crop": wire them into a crop node's x/y/width/height and you can pull the patch out for a detail pass.
Install
It ships in D2 Nodes ComfyUI. Manager → search the pack title → install → restart, or:
cd ComfyUI/custom_nodes
git clone https://github.com/da2el-ai/D2-nodes-ComfyUI
# pip install -r D2-nodes-ComfyUI/requirements.txt in ComfyUI's python env, then restart
Two tiny Python deps (piexif, charset-normalizer), no models. This node is new enough that it arrived in a commit alongside D2 Random Point, so if it isn't in your node list, update the pack - and if you're on a ComfyUI older than the V3 schema era (pre-v32.0.0 of the pack, roughly), update ComfyUI rather than pinning the pack back.
Common issues
"It doesn't randomise." Check the seed's control setting first - on fixed you get the same rectangle forever, which is correct behaviour and also the most common confusion. Second: ComfyUI caches nodes by input signature, and this one dodges that cache precisely because the control rewrites the seed between queues, so a wired-in seed is your job to change. And mind the standing control_after_generate trap - with the control firing after a run, the number in the box is the one the next queue will use, so a good seed is already gone by the time you see the result. ComfyUI's global "widget control mode" set to Before fixes that everywhere.
The mask is a solid band that doesn't fit. Your min size exceeded the canvas and got clamped. A min_width of 4096 on a 1024-wide canvas just becomes 1024.
Downstream complains about the mask shape. The mask is built at the node's width/height, not at whatever your latent is. Wire the same width/height values through, or feed the passthrough outputs where they're accepted.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| width | INT | 10248–16384 | — |
| height | INT | 10248–16384 | — |
| min_width | INT | 5121–16384 | — |
| max_width | INT | 5121–16384 | — |
| min_height | INT | 5121–16384 | — |
| max_height | INT | 5121–16384 | — |
| seed | INT | 00–18446744073709550000 | — |
Outputs (7)
| Name | Type | Description |
|---|---|---|
| width | INT | — |
| height | INT | — |
| mask | MASK | — |
| mask_x | INT | — |
| mask_y | INT | — |
| mask_w | INT | — |
| mask_h | INT | — |