Nodes/ComfyUI-NKD-VFX-Tools/😺NKD Lens Distort
ComfyUI Node

😺NKD Lens Distort

Put a Lens On It — and Take It Off Again, Losslessly

By Nekodificador·Created 6 months ago·Updated a day ago· 155
😺NKD Lens Distort
  • image
  • mask
  • lens_data
  • image
  • lens_data
  • mask
â—„lens_state{}â–º
â—„modedistortâ–º
â—„k10.000â–º
â—„k20.000â–º
â—„k30.000â–º
â—„p10.0000â–º
â—„p20.0000â–º
â—„center_x0.000â–º
â—„center_y0.000â–º
â—„squeeze1.00â–º
â—„zoom1.000â–º
â—„ca_red1.0000â–º
â—„ca_blue1.0000â–º
â—„ca_falloff1.00â–º
â—„vignette_amount0.00â–º
â—„vignette_falloff2.50â–º
â—„edge_modeblackâ–º
â—„compositetrueâ–º
â—„feather8â–º
â—„edge_hardness0.00â–º
â—„diff_threshold0.020â–º

Lens distortion does two jobs, and this node does both. First, the look: barrel and pincushion, chromatic aberration fringing the edges, vignette, anamorphic squeeze - the collection of imperfections that stops a clean render reading as computer-generated. Second, the geometry: straighten a real wide-angle photo so the lines are straight, work on it, and put the exact same lens back afterwards. That second one is the trick, and it's where most lens-distortion tools quietly fail.

How it works

The distortion model is Brown-Conrady, the same one OpenCV calibrates against. k1–k3 are the radial terms (k1 negative is barrel, positive is pincushion; opposite-sign k2 gives that "moustache" edge), p1/p2 are small tangential terms for lens-element decentring, and squeeze is the anamorphic factor (2.0 for a classic anamorphic). Coordinates are normalized by the frame's half-diagonal, so a given k1 means the same amount of barrel on 16:9 as on 1:1 - scaling each axis independently would make the distortion elliptical, which it isn't.

Undistort uses the closed form directly. Distort is the reverse direction and has no closed form, so it's solved by fixed-point iteration - the same trick OpenCV's undistortPoints uses. And it's honest about a real limit: past a fold radius (roughly |k1| > 1/3) the radial map folds over itself and no inverse exists. That's fisheye territory, and it's why the round-trip design matters.

The round trip, and the bit that makes it lossless

The workflow the author built this for: Undistort the plate (lines go straight, the model has an easy job), inpaint it, then Distort back with lens_data connected. lens_data overrides every coefficient widget - you never retype numbers - and it carries the original plate. Why that matters: straightening a fisheye throws the outer ring of the frame outside the output, so rebuilding the photo from the straightened version can never return it. Instead, with composite on, only the masked region you actually edited gets pasted back onto the untouched original. Every other pixel stays bit-identical. That's the difference between "distort round trip" and "quietly lose a quarter of your frame."

The zoom widget is the one that will trip you up: it covers the empty corners the warp leaves, and which way it goes depends on the mode - raise it above 1 in Undistort, lower it below 1 in Distort. Not a bug; zoom is measured in the rectified image so the round trip cancels exactly.

What to set

  • mode - Distort or Undistort. That's the whole direction switch.
  • k1, k2 - the two you'll actually touch. The editor also lets you solve them: trace a few things you know are straight (a roofline, a kerb, a doorframe - three points minimum each) and it finds the coefficients that straighten them.
  • ca_red / ca_blue / ca_falloff - chromatic aberration. 1.0 is off; 1.002 is already a visible edge fringe. ca_falloff keeps the middle clean and concentrates the split in the corners, the way real glass behaves.
  • mask (optional) - in Distort with composite on, this is the region to paste back (normally your inpaint mask). Leave it unconnected and it derives the region from what actually changed.

Outputs: image, lens_data (wire into the node that reverses the warp), and mask (the region carried through).

Install

The whole pack, one shot: ComfyUI Manager, search "NKD VFX Tools", restart. No models, no extra downloads - numpy is the only hard dependency. The one thing to remember when you're stuck is the zoom direction; everything else behaves like the optics textbook it's built from.

Category😺NKD Nodes/3D

Inputs (24)

NameTypeDefaultDescription
imageIMAGE—
lens_stateSTRING{}—
modeCOMBOdistortDistort: bend a clean image as a lens would. Undistort: straighten a real plate.
k1FLOAT0.000-0.75–0.75Main barrel (negative) / pincushion (positive).
k2FLOAT0.000-0.5–0.5Edge correction. Opposite sign to k1 gives moustache distortion.
k3FLOAT0.000-0.25–0.25Extreme-corner term. Only matters near fisheye.
p1FLOAT0.0000-0.05–0.05Tangential: vertical decentring of the lens elements. Keep tiny.
p2FLOAT0.0000-0.05–0.05Tangential: horizontal decentring. Keep tiny.
center_xFLOAT0.000-1–1Optical centre offset, fraction of half-width.
center_yFLOAT0.000-1–1Optical centre offset, fraction of half-height.
squeezeFLOAT1.000.25–4Anamorphic factor. 1.0 for spherical lenses, 2.0 for a classic anamorphic.
zoomFLOAT1.0000.25–4Covers the empty corners the warp leaves. WHICH WAY depends on Mode: in Undistort raise it above 1, in Distort LOWER it below 1. That is not a quirk — zoom is measured in the rectified image, which is what makes it cancel exactly when lens_data feeds the return trip. Open the editor and switch Frame on to see the gaps you are covering.
ca_redFLOAT1.00000.98–1.02Radial scale of the red channel. 1.0 is off; 1.002 is already a visible edge fringe.
ca_blueFLOAT1.00000.98–1.02Radial scale of the blue channel.
ca_falloffFLOAT1.001–6How the fringe ramps out from the centre. 1 is linear; higher keeps the middle of the frame clean and concentrates the colour split in the corners, the way real glass behaves. The corner amount is always CA Red/Blue, whatever the exponent.
vignette_amountFLOAT0.000–1—
vignette_falloffFLOAT2.500.5–6Higher keeps the centre clean and darkens only the corners.
edge_modeCOMBOblackWhat to sample where the warp reaches outside the frame.
compositeBOOLEANtrueDistort mode only, and only once lens_data carries a plate. Instead of rebuilding the photo from the straightened image — which cannot return the outer ring straightening threw away — paste just the masked region back onto the untouched original.
featherINT80–256Soften the edge of the pasted region, in pixels.
edge_hardnessFLOAT0.000–1Push the feathered edge back towards hard.
diff_thresholdFLOAT0.0200–1Only used when no mask is connected: how much a channel must move to count as edited.
maskoptMASKUndistort: the mask is straightened with the image, ready for the sampler. Distort with composite on: the region to paste back onto the plate — normally the same inpaint mask, in the straightened frame. Leave unconnected to derive it from what actually changed.
lens_dataoptNKD_LENSDATAConnect the lens_data of the node that did the outbound warp. When connected it OVERRIDES every coefficient widget above (CA, vignette and edges stay local).

Outputs (3)

NameTypeDescription
imageIMAGE—
lens_dataNKD_LENSDATAWire into the node that reverses this warp. Carries the plate, so the return trip can composite instead of rebuild.
maskMASKThe mask carried through the same warp — straightened alongside the image, or (after a composite) the region that was pasted back.