Nodes/PortraitUtils/Smart Photo Prepare
ComfyUI Node

Smart Photo Prepare

Crop, Protect, and Size a Real Photo for Your Editor

By heyburns·Created about a year ago·Updated 23 days ago· 3
Smart Photo Prepare
  • image
  • editor_target
  • crop_config
  • mask
  • protected_region_mask
  • prepared_image
  • native_crop
  • prepared_mask
  • prepare_info
◄resize_methodbicubic►
◄invert_maskauto►
◄q_left0.005►
◄q_right0.995►
◄q_top0.005►
◄q_bottom0.995►
◄min_span_px8►
◄headroom_ratio0.12►
◄footroom_ratio0.06►
◄side_margin_ratio0.08►
◄bottom_priority0.75►
◄horiz_gravitycenter►
◄framing_tolerance_percent0.5►
◄crop_tolerance_px4►
◄profilefalse►

If you photograph or scan real people and run them through an image editor model, you've built most of this node by hand already: a crop that keeps the face, a pad so the subject isn't jammed against the frame, a resize to whatever resolution the editor wants, and a mask that you had hoped still lined up afterward. Smart Photo Prepare does all of it in one stage, with the crop, the pad, and the resize decided together rather than in three nodes that each undo the last one.

It's the centrepiece of PortraitUtils 2.0 - the node the rest of the pack is arranged around.

What it's for

Editor models - Qwen Image Edit 2.1, FireRed Image Edit 1.1, FLUX.2 Klein Base - each want a specific kind of input: roughly a megapixel (or 1.5, or a 2K budget), dimensions aligned to 16 or 32 pixels, and a frame where the subject is reasonably placed. Feed them an arbitrary 6000×4000 scan and the model either rescales it behind your back or gives you a composition you didn't ask for. This node picks one crop and one output size per the selected editor's own sizing policy, then applies them in a single pass: literal source crop → optional canvas pad → one final resize.

There's also Direct / No Editor, which is the path people forget. It feeds an upscaler instead - the SeedVR2/SUPIR sort of stage - at source scale rather than an editor tier. That's the honest route when a source is degraded and you want detail rebuilt rather than edited, rather than a fallback for "I didn't set up an editor."

The inputs that matter

image, plus two required typed sockets: editor_target (PORTRAIT_EDITOR_TARGET) and crop_config (PORTRAIT_CROP_CONFIG_V2). Those come from Input & Crop Config and the Editor Target Beacon → Active Editor Target pair. Smart Crop on/off, the resolution policy, the forced aspect ratio, and the padding fill all live in the config bundle, not on this node - which keeps the node from growing a second row of sockets. If you're wiring this by hand and the target is a mystery, that's why: the beacon declares it, the Active Editor Target resolves it, and this node consumes it.

The local controls are the fine framing ones: resize_method (bicubic by default; Lanczos is a real floating-point filter here, not a uint8 round-trip through PIL), invert_mask for masks that come out backwards, and then the framing dials - headroom_ratio (0.12), footroom_ratio (0.06), side_margin_ratio (0.08), horiz_gravity, bottom_priority, and the q_left/q_right/q_top/q_bottom quantiles plus min_span_px that decide how faint a mask edge still counts as subject. framing_tolerance_percent (0.5) is the subtle one: it lets two near-identical crops compete on resize quality, so you can end up with a marginally different frame because it upscaled better.

Optional, and the two that earn their socket: mask (your subject mask, from SAM3 or whatever segmenter you run) and protected_region_mask, the author's own description being that connected critical-region masks are protected in every crop mode, including Auto aspect with Smart Crop off. That's the feature that makes this usable for portraits where the head must not be clipped. Wire a head prompt mask into it and the planner treats that region as a hard constraint - with a 1% pad on the short side plus your safety margins preserved toward the nearest source edge. It's a lazy input, so if you connect it, the segmentation runs whenever preparation runs.

The outputs

Four sockets, and only one of them is the image you usually want.

prepared_image is the editor-ready frame - editor resolution, padding included, forced aspect exact. That goes to the editor. native_crop is the exact source-pixel crop before padding or resizing, which makes it the honest answer to "how much of my photograph actually survived," and it's the one to eyeball when a result looks soft. prepared_mask is your subject mask cropped, padded (added mask area is always black), and resized onto the same grid. prepare_info is the typed PORTRAIT_PREPARATION_INFO_V2 bundle that everything else in the Readers category unpacks.

One wiring rule from the author, and it's the one people break: don't put another crop or scale node after this. In particular, if you copied FireRed's example workflow, bypass FluxKontextImageScale - its centre crop will happily undo your protected region and forced aspect.

Install

Manager → search PortraitUtils → install → restart the ComfyUI server (refreshing the browser won't reload Python). Or:

cd ComfyUI/custom_nodes
git clone https://github.com/heyburns/PortraitUtils
/path/to/ComfyUI/venv/bin/python -m pip install -r PortraitUtils/requirements.txt

Windows portable: use python_embeded. Dependencies are just numpy, Pillow, opencv-python-headless, and tifffile - install one OpenCV variant only, since the wheels share the cv2 namespace. The editor models, segmenter, and upscaler are separate installs; this pack ships no weights.

Note the 2.0 breakage if you're upgrading: the old 13-output and compact preparation IDs are gone, with no aliases, so saved workflows will show missing nodes until you replace them.

Where people get burned

Masks on the wrong grid. If your subject mask was generated before border removal, it won't match the image this node receives and preparation will report the mismatch rather than stretching it. Generate masks after AutoCrop, or trim both identically.

Assuming protected regions are advisory. They're mandatory. A plan that would crop inside the protected bbox is rejected outright - including with Smart Crop off - so if you're suddenly getting errors, check what you fed the protected socket before you blame the sizing.

Expecting padding to be outpainting. It's deterministic: Edge extension repeats the nearest edge, or you get solid black/white. Nothing is generated.

Trusting prepared_image dimensions to be the whole story. They're exact, but the stock Qwen Plus encoder still resizes its conditioning reference to roughly 1 MP internally, so a 1.5 MP prepared frame doesn't automatically give the editor 1.5 MP of source detail.

CategoryPortraitUtils/Photo Edit V2

Inputs (20)

NameTypeDefaultDescription
imageIMAGE—
editor_targetPORTRAIT_EDITOR_TARGET—
crop_configPORTRAIT_CROP_CONFIG_V2—
resize_methodCOMBObicubic4 options: bicubic, lanczos, area, bilinear
invert_maskCOMBOauto3 options: auto, false, true
q_leftFLOAT0.0050–0.2—
q_rightFLOAT0.9950.8–1—
q_topFLOAT0.0050–0.2—
q_bottomFLOAT0.9950.8–1—
min_span_pxINT81–2048—
headroom_ratioFLOAT0.120–0.45—
footroom_ratioFLOAT0.060–0.45—
side_margin_ratioFLOAT0.080–0.45—
bottom_priorityFLOAT0.750–1—
horiz_gravityCOMBOcenter3 options: center, left, right
framing_tolerance_percentFLOAT0.50–10Allows similarly framed safe crops to compete on resize quality; fallback pixel counts take priority.
crop_tolerance_pxoptINT40–64When no safe crop fits, allow at most this many source pixels per edge into available safety margins. 0 always preserves them. The protected-region mask stays intact.
maskoptMASK—
protected_region_maskoptMASKConnected critical-region masks are protected in every crop mode, including Auto aspect with Smart Crop off.
profileoptBOOLEANfalseRecord host stage timings in info. No CUDA synchronization or model-memory changes.

Outputs (4)

NameTypeDescription
prepared_imageIMAGE—
native_cropIMAGE—
prepared_maskMASK—
prepare_infoPORTRAIT_PREPARATION_INFO_V2—