Nodes/ComfyUI CV/CV Pinch/Stretch Map
ComfyUI Node

CV Pinch/Stretch Map

Pinch and stretch a frame without a lens — a border-anchored remap, per axis

By bmad4ever·Created 4 months ago·Updated 16 days ago· 1
CV Pinch/Stretch Map
  • map_x
  • map_y
  • map_x
  • map_y
◄power_x1.50►
◄power_y1.50►
◄center_x0.50►
◄center_y0.50►

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:

  1. CV Identity Map (from your image, or sized explicitly).
  2. CV Pinch/Stretch Map - optionally chained with CV Wave Map, CV Displacement Map, and the others in the same module, each one composing onto the previous map.
  3. cv2_remap with your image and map_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. > 1 pinches (compresses content toward the center), < 1 stretches (spreads it toward the edges), 1 is identity. The two axes are independent, so power_x = 2, power_y = 1 squeezes 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 borderMode on your cv2_remap node, not this one. BORDER_CONSTANT for a clean fill, reflections if you want them.
  • The whole image looks rescaled instead of pinched - power and center are easy to mix up. center at 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.

Categoryimage/CV/remap

Inputs (6)

NameTypeDefaultDescription
map_xNPARRAYIncoming source-x coordinate map to distort. Start the chain with 'CV Identity Map'; further remap nodes compose onto it.
map_yNPARRAYIncoming source-y coordinate map (must share map_x's shape - both come from the same chain).
power_xFLOAT1.500.05–20> 1 pinch, < 1 stretch, 1 = identity.
power_yFLOAT1.500.05–20> 1 pinch, < 1 stretch, 1 = identity.
center_xoptFLOAT0.500–1Pivot as a fraction of the width.
center_yoptFLOAT0.500–1Pivot as a fraction of the height.

Outputs (2)

NameTypeDescription
map_xNPARRAY—
map_yNPARRAY—