Conditioning-crop-zoom-SDXL
One node that rewrites your conditioning's framing
- positive
- negative
- latent
- positive
- negative
The name is a small lie: this node does not crop or zoom anything. It rewrites the size/crop metadata sitting on your conditioning, so SDXL thinks the rendered frame is a sub-crop of a larger source image. That sounds like fluff, but it's a genuine quality lever, and the author of this pack argues it should be the final stop on both your positive and negative conditioning before the sampler.
Why this matters
SDXL was trained with non-zero crop coordinates on a large fraction of its data. When you claim crop=0 and source=target (which is what a default stock encoder does), you're sampling from a thinly-trained slice of the model's conditioning distribution. Tell it the frame is a window into a bigger image and you get a "free zoom bias" - empirically, often better-composed, higher-detail output. The node also derives target_width/target_height from your actual latent, so you can't accidentally drift between rendered size and conditioning size when you change resolution. Stock CLIPTextEncodeSDXL hardcodes those to defaults that may not match your latent.
The mechanism
The math is simple. target = latent W/H (the latent is at 1/8 scale, so it's multiplied by 8), source = round(latent * zoom), and crop = (offset + 1)/2 * (source - target) per axis. It writes both ComfyUI's flat keys (width/height/crop_w/crop_h/target_width/target_height) and the SDXL-paper tuple form (original_size_as_tuple/crop_coords_top_left/target_size_as_tuple) so any downstream reader is happy. Every other metadata key - start_percent, end_percent, pooled_output, strength - is preserved.
Inputs and outputs
positive(CONDITIONING) - your positive-prompt conditioning.negative(CONDITIONING) - your negative; gets the same metadata written. Leave it unconnected and thenegativeoutput comes out empty.latent(LATENT) - read only for W/H. Wire the same latent your sampler uses.zoom(FLOAT, min 1.0, default 1.0) - how much larger to claim the source frame is.offset_x/offset_y(FLOAT, -1..1, default 0) - where the target window sits in that larger source. -1 = far left/top, 0 = centered, +1 = far right/bottom.
Outputs: positive and negative, both CONDITIONING, straight into your KSampler.
Placement - the one rule that matters
Put it at the very end of your conditioning graph, with both positive and negative wired through, and connect its outputs to the sampler's positive and negative. Even untouched defaults (zoom 1.0, offsets 0) are an improvement, because you've pinned target/source to the real latent size on both sides in one node. Crank zoom to 2.0 with centered offsets and you get the classic "wider source, tighter framing" composition bias.
Install
ComfyUI Manager → search "Unified Conditioning". Or:
cd ComfyUI/custom_nodes
git clone https://github.com/RandyHaylor/unified-conditioning-combine-with-timestep-range.git unified-conditioning-merge
Restart ComfyUI. It shows up under unified-conditioning-merge. No models, no extra dependencies.
Troubleshooting
- It doesn't touch the latent, so don't expect an actual zoom-in. If you want that, use a real upscale/crop node; this only biases the model's framing assumption.
- Get the offsets backwards?
offset_x=-1means the target window sits toward the left of the source - i.e. the rendered frame shows the left part of the implied bigger image. Read it as "where is my frame inside the larger image." - If your sampler ignores it, check the node is downstream of everything else on both sides - anything that re-encodes after it (like a second CLIPTextEncode) overwrites the metadata.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| positive | CONDITIONING | — | |
| negative | CONDITIONING | — | |
| latent | LATENT | — | |
| zoom | FLOAT | 1.001–100 | — |
| offset_x | FLOAT | 0.00-1–1 | — |
| offset_y | FLOAT | 0.00-1–1 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| positive | CONDITIONING | — |
| negative | CONDITIONING | — |