D2 Random Point
One random X/Y pair, in pixels or as a fraction
- width
- height
- x
- y
A random point is a small thing to want, and typing coordinates by hand is a small annoyance - until you're running a workflow forty times and quietly reusing the same crop origin for all forty. D2 Random Point outputs one X/Y pair, as absolute pixels or as a 0–1 fraction, re-rolled every queue. No canvas, no mask, no pixels.
What it's for
Coordinate sockets. Anywhere a node asks you to type an x and a y - a crop origin, a paste position, a region anchor - right-click that widget, hit Convert widget to input, and drop this node's x/y onto the new sockets. Now the position is defined once and randomised per run instead of retyped.
If you know where you want the point, this is the wrong tool - D2 Create Point, same pack, gives you a canvas to click on. Random Point is for when any point in a range will do.
The outputs are typed as the wildcard *, deliberately: the runtime type changes with the mode, so a wildcard socket means flipping mode or number_type doesn't tear your wires out, and x plugs into an INT or FLOAT input regardless. It also means ComfyUI won't warn you if the consumer wanted a different type.
How it works
The design decision worth understanding: min_x/max_x/min_y/max_y are always fractions, 0.0 to 1.0, in both modes. The node draws a uniform random value in that range, then converts. So your box keeps meaning the same part of the frame when width/height change, and switching mode doesn't move anything - 0.75 relative is 768 absolute at a width of 1024.
The conversion:
mode: absolute+number_type: int→int(round(rel * size))- a whole pixel.mode: absolute+number_type: float→rel * size, unrounded.mode: relative→ the fraction itself, andnumber_typeis ignored. Rounding a fraction to an int would leave you with 0 or 1, so the author skips it.
Reversed ranges get swapped instead of erroring, and out-of-range values get clamped - the widget limits you, upstream wired inputs don't.
Inputs and outputs
What you actually set:
width/height- the coordinate space, default 1024×1024, multiples of 8. This is what "absolute" is absolute to; it's not a canvas, the node draws nothing.min_x/max_x/min_y/max_y- the box the point can land in, as fractions, step 0.01. Defaults cover the whole frame.min_y: 0.6, max_y: 0.95gives you "somewhere in the lower third", which is the kind of thing this node is actually for.mode-absoluteorrelative.number_type-intorfloat, absolute mode only.seed- carriescontrol_after_generate, which is what makes it re-roll between queues.fixedreproduces a result.
Outputs are width, height, x, y. The size passthroughs exist so a downstream crop or latent can be sized from the same place as the point rather than from a second pair of widgets.
Install
Part of D2 Nodes ComfyUI. 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 inside ComfyUI's python env, then restart
The pack's requirements.txt is two tiny packages (piexif, charset-normalizer) and there are no model downloads. Install them even though this node needs neither - piexif is imported when the pack's image module loads, so a bare clone can take the whole D2/Image category down, this node included. Since v32.0.0 the pack targets ComfyUI's V3 node schema, so you need a current ComfyUI; on an old one, update the app, not the pack.
Common issues
Same point every run. The seed's control is on fixed - that's also how you get reproducibility, find a point you like and lock it. And note the standing control_after_generate trap: with the control firing after a run, the number shown is the seed the next queue will use, so flip ComfyUI's global "widget control mode" to Before if you plan on catching good seeds. (ComfyUI caches nodes by input signature, and that seed control is exactly what changes it - convert the seed into a wired input and the node stops re-rolling on its own.)
x is always 0 or 1. You're in relative mode expecting pixels, or in absolute mode with the range still at its defaults and a tiny width. Check mode first.
Downstream wants an int and gets a float. Wildcard sockets don't validate; set number_type to int when the consumer is a pixel-index widget. And note absolute + int can return exactly width or height - the range is inclusive of the edge, not size - 1 - so keep a little margin if your consumer indexes pixels.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| width | INT | 10248–16384 | — |
| height | INT | 10248–16384 | — |
| min_x | FLOAT | 0.000–1 | — |
| max_x | FLOAT | 1.000–1 | — |
| min_y | FLOAT | 0.000–1 | — |
| max_y | FLOAT | 1.000–1 | — |
| mode | COMBO | absolute | 2 options: absolute, relative |
| number_type | COMBO | int | 2 options: int, float |
| seed | INT | 00–18446744073709550000 | — |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| width | INT | — |
| height | INT | — |
| x | * | — |
| y | * | — |