CV Pinch/Stretch Map
Pinch and stretch a frame without a lens — a border-anchored remap, per axis
- map_x
- map_y
- map_x
- map_y
Barrel distortion, lens breathing, anamorphic squeeze, the fisheye-ish look you'd otherwise fake with a lens emulation - all of it is coordinate math, and this node exposes one clean family of it: a per-axis power curve that never moves the frame border.
The property that makes it nice: coordinates are remapped by s' = 1 - (1 - |s|)^power on each side of the chosen center, so the edges stay exactly where they are and the effect stacks toward the middle. Nothing floats away, nothing needs re-cropping, and a sparse grid stays a grid.
The remap-chain pattern you need to know first
This node doesn't touch pixels. It's part of the pack's map generator family, and it follows the convention its module documents: CV Identity Map emits the do-nothing coordinate grid sized like your image, every distortion node rewrites the coordinates fed into it, and the chain terminates at the raw cv2_remap node which samples the image (use borderMode=BORDER_CONSTANT unless you want edge reflections).
So the wiring is:
CV Identity Map(from your image, or sized explicitly).CV Pinch/Stretch Map- optionally chained withCV Wave Map,CV Displacement Map, and the others in the same module, each one composing onto the previous map.cv2_remapwith your image andmap_x/map_y.
That composition property is why map_x and map_y come back out of this node as socket outputs rather than being applied internally. A chain of these is one click-drag per distortion, and the order you chain them in is the order the math composes.
Inputs and outputs
map_x/map_y- the incoming coordinate maps, both from the same chain and required. They must share a shape; the node raises a clear error naming both shapes if they don't, because mixing maps from two chains is the one mistake that produces plausible-looking garbage.power_x,power_y(default 1.5) - the effect strength per axis.> 1pinches (compresses content toward the center),< 1stretches (spreads it toward the edges),1is identity. The two axes are independent, sopower_x = 2, power_y = 1squeezes horizontally only - that's your anamorphic look, or a cheap "made for 4:3, shown on 16:9" fix.center_x,center_y(advanced, 0.5) - the pivot as a fraction of width/height. Off-center makes the effect asymmetric, which is how you get a lens-ish falloff toward one side rather than a symmetric barrel.
Outputs are just map_x and map_y, ready for the next distortion node or for cv2_remap.
Coordinates pushed outside the source frame are left alone and valid - cv2_remap fills them with the border - so pushing pixels off-canvas doesn't produce a mathematical error, just an edge smear you may or may not want.
Install
Manager → search ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart. Python ≥ 3.12, V3 node API. This module is honestly described in the pack's own docs as closed-form Python + numpy with no cv2 in the math at all - it just speaks the NPARRAY dialect.
Common issues
- "map_x and map_y must have the same shape" - you fed maps from two different chains, or one branch got resized. Both maps must always come from the same chain.
- "map_x is required - start from 'CV Identity Map'" - you skipped the chain's head. There's no implicit grid.
- A hard border or edge repeat in the result - that's
borderModeon yourcv2_remapnode, not this one.BORDER_CONSTANTfor a clean fill, reflections if you want them. - The whole image looks rescaled instead of pinched -
powerandcenterare easy to mix up.centerat 0.5 is the middle; a value near 0 or 1 anchors the effect on an edge and looks like a shift. - Composing gives unexpected results - chaining composes in wiring order and each node rewrites the incoming coordinates, so reordering nodes is not commutative. Animate a parameter to see which way it's going before you commit.
The pack caveat applies as always - LLM-assisted, personal, no production guarantee. For map math you can preview in one remap, that's an acceptable risk.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| map_x | NPARRAY | Incoming source-x coordinate map to distort. Start the chain with 'CV Identity Map'; further remap nodes compose onto it. | |
| map_y | NPARRAY | Incoming source-y coordinate map (must share map_x's shape - both come from the same chain). | |
| power_x | FLOAT | 1.500.05–20 | > 1 pinch, < 1 stretch, 1 = identity. |
| power_y | FLOAT | 1.500.05–20 | > 1 pinch, < 1 stretch, 1 = identity. |
| center_xopt | FLOAT | 0.500–1 | Pivot as a fraction of the width. |
| center_yopt | FLOAT | 0.500–1 | Pivot as a fraction of the height. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| map_x | NPARRAY | — |
| map_y | NPARRAY | — |