DiffMorpherNode
Make two images melt into each other without the creepy-face crossfade
- image_0
- prompt_0
- image_1
- prompt_1
- GIF
You've got two images and you want a GIF of one turning into the other. A plain crossfade dissolves into a melted blur at the halfway point - faces lose their structure, colors wash out, and it looks like a 2003 PowerPoint transition. DiffMorpherNode exists to fix that. It wraps the DiffMorpher research project (Kevin-thu/DiffMorpher) into a single ComfyUI node: feed it two images and two prompts, get back a morph GIF where the subject stays recognizable through every intermediate frame. It's not a video model and it's not frame interpolation - it's diffusion-based morphing, and nothing else in the ecosystem does it quite like this.
How it actually works
The whole trick is that it never blends pixels directly. Instead it:
- Inverts both images into the latent space of Stable Diffusion 2.1 base using DDIM inversion, capturing each image's noise latents and its self-attention maps along the way.
- Spherically interpolates the latents (slerp) at each step along the morph. The
use_adaintoggle borrows a trick from latentblending: it re-normalizes the interpolated latents so the mid-frames don't drift in color and contrast. - Replaces the up-block self-attention maps with a blend of the two originals' maps. That's what keeps the structure of the subject from collapsing - a cat stays a cat while becoming a dog.
lamb(0–1, default 0.6) controls how much of the attention is replaced; higher means more aggressive replacement, and you'll feel it in how "locked-on" the mid-frames are. - Optionally interpolates a pair of LoRAs. This is DiffMorpher's headline idea: fit a tiny LoRA to each image so each subject is preserved faithfully, then interpolate the LoRA weights frame by frame. It makes the results dramatically better, but there's a catch below.
- Optionally reschedules the alphas (
use_reschedule). It renders a first pass, measures the perceptual distance between adjacent frames with LPIPS, then redistributes yournum_framesso fast-moving sections of the morph get more frames. Roughly doubles your sampling time - worth it for organic motion, skip it when you're iterating.
Everything runs at 512×512: your inputs are center-cropped and resized before sampling, so compose accordingly or the edges get lopped off.
The inputs that matter
The two image_0/image_1 pairs and their prompt_0/prompt_1 texts are the obvious ones - the pack ships a TextNode you can use for the prompts. Past that:
num_frames(16) - GIF length. Each frame is a full diffusion pass, so this is your main speed lever.duration(100) - milliseconds per frame. Lower it to speed the animation up; the node outputs a GIF, so this is your framerate.use_adain,use_reschedule- leave both on; they're the defaults in the actual code, and they're the difference between a decent morph and a good one.save_inter- dumps a numbered PNG per frame intoComfyUI/output/diffmorpher. The README says it defaults to off, but the shipped code defaults it to on, so expect a folder of frames to appear every run.lora_0/lora_1- see the trap below.
The output is a single GIF (a file path). Wire it into the pack's PreViewGIF node to see it in the UI, because DiffMorpherNode isn't an output node on its own.
Installing
Standard custom-node deal:
cd ComfyUI/custom_nodes
git clone https://github.com/AIFSH/DiffMorpher-ComfyUI.git
cd DiffMorpher-ComfyUI
pip install -r requirements.txt
requirements.txt pulls in diffusers, transformers, accelerate, opencv_python, lpips, and friends. Two downloads happen on first run: the SD 2.1 base model (a multi-gigabyte fetch from Hugging Face into models/diffusers/stable-diffusion-2-1-base, fp16 weights only) and LPIPS's AlexNet weights. ComfyUI Manager finds the pack if you search "DiffMorpher".
Where people get burned
- Random Civitai LoRAs won't work. This is the most-reported gotcha, and it's by design:
lora_0/lora_1expect DiffMorpher's own per-image LoRAs, trained per-image with the parent repo'strain_lora.py. Plugging in an arbitrary character LoRA does nothing useful. Worse, the code only enables LoRA interpolation when both paths are set - give it one and it silently runs LoRA-free. - It's slow. 16 frames is 16+ sampling passes plus two inversions, and
use_rescheduledoubles it. Budget minutes per morph even on a decent card. - GPU only. It runs fp16 on CUDA with no fallback; this won't fly CPU-only.
- First run feels broken. That multi-GB model download happens inside the node on first execution, so the queue hangs for a while before anything appears.
The author (AIFSH, a prolific Chinese wrapper-pack maker) keeps the project minimal and answers issues on GitHub; the README's Windows section offers a paid one-click package via WeChat, but you don't need it - the pip install above works fine.
Inputs (13)
| Name | Type | Default | Description |
|---|---|---|---|
| image_0 | IMAGE | — | |
| prompt_0 | TEXT | — | |
| image_1 | IMAGE | — | |
| prompt_1 | TEXT | — | |
| num_frames | INT | 16 | — |
| duration | INT | 100 | — |
| use_adain | BOOLEAN | true | — |
| use_reschedule | BOOLEAN | true | — |
| lamb | FLOAT | 0.600–1 | — |
| save_inter | BOOLEAN | true | — |
| diffusers_modelopt | COMBO | 0 options: | |
| lora_0opt | COMBO | 0 options: | |
| lora_1opt | COMBO | 0 options: |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| GIF | GIF | — |