Seamless Border Crop
The node that finishes the job JN_SeamlessBorder started
- image
- params
- IMAGE
Making a genuinely tileable texture in ComfyUI is a two-act play. Act one is JN_SeamlessBorder, which patches your model so the edges of the latent actually match up during sampling. Act two is this node, JN_SeamlessBorderCrop, which takes the finished image and trims off the border that the trick added. Nobody names the cleanup step, which is why your "seamless" textures keep coming out with a visible seam down one edge - you generated with the patch but never cropped.
This node is exactly one job: it takes an IMAGE plus a SEAMLESS_PARAMS object and crops the image, returning a clean IMAGE you can tile forever.
How it works
The mechanism is dumb in the best way. JN_SeamlessBorder wraps your model so that during sampling it copies the border strips of the latent to the opposite edges - a circular-ish padding trick that makes the left edge agree with the right edge and the top with the bottom. The cost is that the outer gap pixels of the result are duplicate, fake border, not real generation. This node just slices them back off.
The key detail: it reads direction and border_percent straight out of the SEAMLESS_PARAMS dict passed in. You never set a border size here. You set it once on JN_SeamlessBorder - border_percent defaults to 0.125, direction to "both" - and this node quietly crops the same amount. For each dimension it computes gap = round(size * border_percent), capped at a quarter of the size so a badly-set value can't eat the whole image, then slices gap:-gap from both sides.
Wiring it up
The only two inputs are image and params, and there's nothing to fiddle with:
- image - the decoded output of your VAE, post-sampling.
- params - wire it from the
SEAMLESS_PARAMSoutput ofJN_SeamlessBorder. This is what keeps crop and patch in sync.
The chain is JN_SeamlessBorder → its patched MODEL goes to your KSampler, its SEAMLESS_PARAMS goes to this node, and VAE Decode feeds the image input. Output IMAGE goes to a Save/Preview node.
Where it belongs
Game-asset people, texture artists, and anyone making tiling backgrounds will get the most out of it. Pair it with JN_Seamless (the all-in-one that does both jobs in a single node) if you'd rather not think about the split, but the Border + Crop pair is the more controllable route because you can set start_percent/end_percent on the Border node to only apply the patch over part of the denoising.
Install
JNComfy is one pack with a lot of nodes, and this one rides along with it. Easiest: ComfyUI Manager → search "JNComfy" (the pack title) → Install, then restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/jn-jairo/jn_comfyui
restart ComfyUI, and if you're installing manually run pip install -r requirements.txt inside the folder. Note the README's requirements list is heavy - torchaudio, librosa, transformers, rembg, spandrel - but that's all for the pack's audio and face-restore sections. This node is pure tensor slicing; it needs none of it. Expect the install to take a while anyway, because Manager pulls the whole list.
Gotchas
- The crop amount is decided entirely by the
border_percentonJN_SeamlessBorder. Change one and not the other and you'll either crop off real pixels or leave a sliver of mirrored edge behind. That sync is the whole point of passingSEAMLESS_PARAMSthrough. - It needs that params object - feed it a bare image and it errors, since
paramsis required. - The pack patches ComfyUI internals on startup. If your console shows "Failed to import module" lines after install, that's usually another patch-heavy pack colliding with JNComfy, not this node misbehaving.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| params | SEAMLESS_PARAMS | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |