WAS Adaptive Difference Latent Upscale (Damped)
A latent upscale that dodges ringing at edges
- latent
- latent
- mask
- mask_preview
Naive latent upscaling - the plain nearest/bilinear resize most people reach for before a second KSampler pass - tends to ring and halo at hard edges, exactly where you'd most want the upscale to look clean. This node is built specifically to avoid that: it computes where the strong edges are, backs the upscale enhancement off around them, and leaves you a mask so you can actually see where it's protecting detail versus pushing it.
How it works
The base of it is a standard interpolated upscale at scale, using smooth_mode (bilinear, bicubic, or area). On top of that sits an "adaptive difference" system: the node measures how much the upscale changes the image locally, blurs that difference signal (diff_blur_sigma), and gates how strongly the difference gets applied using threshold/softness/weight_power - this is the general enhancement-strength control, before edges are even considered.
The edge protection specifically lives in the enable_directional_damping block. When it's on (default), the node builds a second, edge/energy-based mask via damping_gate_mode, damping_threshold, damping_softness, and damping_power, then uses damping_strength to scale down enhancement wherever that mask says "there's a strong edge here." damping_grad_blur_sigma and damping_mask_blur_sigma smooth that gradient/mask before it's applied, and damping_highpass_sigma isolates the high-frequency edge signal it's reacting to. For video latents, temporal_ema and damping_temporal_ema exponentially smooth both signals across frames, so the damping doesn't flicker frame to frame the way a per-frame-only calculation would.
If this sounds like a lot to internalize before you've upscaled anything - it is. Start with just scale and smooth_mode, leave everything else at default, and only reach for the damping controls once you can see a specific artifact you're trying to fix.
The inputs and outputs that matter
latent(LATENT) - what you're upscaling.scale(default 2, range 1–8) - the upscale factor. General latent-upscale convention favors modest factors (roughly 1.5–2x) per pass rather than pushing one huge jump - you can always chain a second pass.smooth_mode- bilinear/bicubic/area, the base interpolation before any adaptive/damping logic runs.damping_strength(default 0.35) - the single most important damping knob once you've decided edge protection needs adjusting. Higher backs off enhancement near edges more aggressively.preview_mode(weight / damp / both) - controls whatmask_previewactually shows you.
Three outputs: latent (the upscaled result, feed straight into your second-pass KSampler), mask (the computed weighting/damping mask as a MASK), and mask_preview (an IMAGE visualization of that mask, so you can actually see what the node did rather than guessing from the final result).
How to install it
Via ComfyUI Manager: search "WAS_Extras", install, restart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/WAS_Extras
Restart ComfyUI. No extra dependencies for this node.
Common issues & troubleshooting
Upscaled result looks flat or mushy, like detail got smoothed away. Damping is likely too aggressive for your image - lower damping_strength, or check mask_preview (set preview_mode to damp) to confirm the node thinks more of the image is "edge" than you'd expect.
Still seeing ringing or halos at edges despite damping being enabled. Check enable_directional_damping is actually on, then try lowering damping_threshold so more of the image counts as "edge enough" to protect, or raise damping_strength itself.
Video latents flicker between frames. That's what temporal_ema and damping_temporal_ema are for - both default to smoothing values above zero already (0 and 0.25 respectively), but if you're still seeing frame-to-frame instability, raise damping_temporal_ema further before touching anything else.
Too many parameters, not sure which one is causing a given artifact. Use mask_preview as your diagnostic first. preview_mode: both shows you the general enhancement weighting and the edge-damping mask together - it's faster to look at what the node computed than to guess from the final upscaled latent alone.
Inputs (21)
| Name | Type | Default | Description |
|---|---|---|---|
| latent | LATENT | The latent to enlarge. A video latent with a time axis is handled frame by frame. | |
| scale | FLOAT | 2.001–8 | How much larger the result is. 2.0 doubles both sides, 1.5 adds half again, 1.0 leaves the size alone and only applies the damping. Sizes are rounded to whole latent blocks. |
| smooth_mode | COMBO | bilinear | How the smooth half of the blend is enlarged. `bilinear` is the safe default; `bicubic` is sharper and can overshoot slightly at a hard edge; `area` averages the source region and is the softest of the three. |
| diff_blur_sigma | FLOAT | 0.600–8 | How far the disagreement between the two enlargements is spread before it decides anything, in latent blocks. A little blur keeps the blend from switching on and off block by block; 0.0 uses the raw per-block difference and gives the busiest map. |
| threshold | FLOAT | 0.1200–1 | How much disagreement counts as detail. Below this the blocky enlargement is kept, above it the smooth one takes over. Lower values smooth more of the picture; 0.12 leaves flat areas crisp and treats edges. |
| softness | FLOAT | 0.0500.0005–1 | How gradual the changeover at the threshold is. Small values such as 0.005 give a hard switch that can be seen as a rim; 0.05 fades between the two enlargements over a comfortable range. |
| weight_power | FLOAT | 1.000.1–6 | Bends the blend map after it is built. Above 1.0 pulls it towards the blocky enlargement everywhere but the strongest edges; below 1.0 spreads the smooth enlargement into weaker detail. 1.0 leaves the map as measured. |
| weight_blur_sigma | FLOAT | 0.000–8 | Softens the finished blend map, in latent blocks. Raise it when the treated areas have visible outlines of their own; 0.0 leaves the map alone. |
| temporal_ema | FLOAT | 0.000–0.99 | How much each frame of a video latent carries over from the frames before it, which stops the blend map flickering. 0.0 treats every frame on its own; 0.5 is a light smoothing; 0.9 is heavy and can smear the map behind fast motion. Ignored on a single image. |
| enable_directional_damping | BOOLEAN | true | Whether the second pass runs, which takes fine detail back out along strong boundaries. It is what removes the halo that an upscale leaves around hard edges. Turn it off to see the blend on its own, or when the source is already soft. |
| damping_strength | FLOAT | 0.350–1 | How much fine detail is removed where the damping mask is fully on. 0.0 removes none and turns the pass off; 0.35 takes the edge off a halo; 1.0 flattens the detail there completely. |
| damping_gate_mode | COMBO | weight_sqrt | Where the damping is allowed to act. `none` lets it act on every boundary it finds. `weight` confines it to the areas the blend already treated. `weight_sqrt` is in between, allowing some damping in areas the blend touched only lightly. |
| damping_grad_blur_sigma | FLOAT | 0.000–8 | Blur applied before boundaries are looked for, in latent blocks. Raise it so that texture is not mistaken for an edge; 0.0 finds the finest boundaries. |
| damping_threshold | FLOAT | 0.2500–1 | How strong a boundary has to be to be damped, measured against the strongest one in the picture. 0.25 catches the clear outlines; lower values reach into texture as well. |
| damping_softness | FLOAT | 0.0800.0005–1 | How gradually the damping fades in around that threshold. Small values give a hard-edged mask; 0.08 fades over a comfortable range. |
| damping_power | FLOAT | 1.000.1–6 | Bends the damping mask. Above 1.0 confines the damping to the very strongest boundaries; below 1.0 spreads it over more of the picture. 1.0 leaves the mask as measured. |
| damping_mask_blur_sigma | FLOAT | 0.600–8 | Softens the damping mask before it is used, in latent blocks. A little blur keeps the damped strip from having a visible border of its own; 0.0 uses the mask as found. |
| damping_highpass_sigma | FLOAT | 1.000–8 | Which detail counts as fine enough to be removed, in latent blocks. 1.0 takes out ringing while leaving the shapes; larger values reach into broader structure and start to blur. 0.0 removes the whole signal under the mask instead. |
| damping_temporal_ema | FLOAT | 0.250–0.99 | How much of the damping mask each frame of a video latent carries over from the frames before it, so damped areas do not shimmer. 0.0 treats every frame on its own. Ignored on a single image. |
| preview_mode | COMBO | both | Which map leaves the node. `weight` shows where the smooth enlargement was used, `damp` shows where fine detail was removed, `both` puts the two side by side in the preview and sends the damping map to the mask output. |
| output_mask_pixel_scale | INT | 81–16 | How many preview pixels each latent block becomes. 8 matches the size the latent decodes to on most VAEs, so the preview lines up with the finished picture; 1 gives the small raw map. This affects the preview image only, never the mask output. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | The enlarged latent, ready for a sampler or a decode. |
| mask | MASK | The map the node worked from, at latent resolution: the damping map on `damp` and `both`, the blend map on `weight`. Useful for driving another node from the same areas this one treated. |
| mask_preview | IMAGE | The same map as a viewable image, enlarged by output_mask_pixel_scale. On `both` the blend map is on the left and the damping map on the right. |