geometer
5th April 2026, 15:20
This is a fairly short function that targets ringing or echo-artifacts from sharp edges. It does not blur, and also seeks to protect little image structures close to the size of the artifacts.
Here is the most recent update from Aug, 2026: https://forum.doom9.org/showthread.php?p=2049482#post2049482 (improves pr2,pr3 functionality)
This function is recommended, when you remaster or recompress old DVDs and try some sharpening, eg. with convolutional sharpeners.
You can use this function freely, but please when you store it somewhere, add the line "Hans' ringing remover".
Technical functionality:
What it does, it analyzes the signal and predicts one artifact to the left and one to the right of a vertical edge.
Each predicted artifact has a positive and a negative lobe, mimicking the decay of a wave form (each is one sample only, relying on proper filtering).
You can add the function a second time to tackle ringing with longer tails.
The placement of the artifact is set by the offset parameters.
The predicted artifact thus is being reengineered, and then subtracted from the original.
In other words, you have to set the weight parameters, and monitor the effect on a particular echo or Gibbs ringing structure.
The task is to zero in weight, not too light, not too dark, to make the artifacts less visible.
It is entirely possible to use negative weight, because some processes in recording and video production may cause negative artifact polarity.
Quality:
The quality level is DVD-related, but regarding Avisynth script processing, it was configured and tested with planar formats like YUV444 and 12-bit resolution.
I think we need also some upsampling. I routinely go from DVD to 960x640, then finally dithered down to 8bit for the mpeg encoding. Good compromise between quality and disk space.
Anti-ringing needs some finer granularity, I would not recommend to do the math at 8bit 720x480. YMMV.
Using the function at higher than double-size upsampling also is not recommended. Operating at 4k level would require to include wider signal windows and quadruple amount of autocorrelation/FIR signal taps (feel free to implement).
It has been tested successfully at 12-bit, 720x480, 960x640, 1500x960 (exact size does not matter but should be mod 4).
You can tackle different artifacts at different upsizing levels, take what fits best.
The correction should be applied on the Luma signal, but you can experiment as you like.
Hints for operation:
Find the dominant ringing distance first (change offset while watching a strong edge).
This means you look at the pixel distance between edge and artifact, and set offset to 1 .. 7, depending on which one is the most disturbing of several echoes.
In some cases, also in upsampled mode, offset values of 0 or even 5 to 7 may fit the issue. Negative offset is experimental, eg. for special effects.
Expect to run one or two instances with different offsets, and also at different resampling levels.
Tune in on a strongly visible artifact that is repetitive through the video. Start with a weight between 0.5 and 1.0, then try out offsets.
Great when you find out you can reduce weight values after getting the offsets right.
In short: Find the right offset, then nudge the weight of the corrective signal.
Negative weight can be used as well, because in video recording and processing, artifacts can come with switched polarity.
It may be rewarding to let some subtle remainder of ringing artifacts be handled by the x264 denoiser and not try to cancel out everything exactly.
Dynamic, protective operation:
The algorithm seeks to avoid blurring and also messing-up of small shapes in the image.
For this, pr2 and pr3 control protection of local signal structures at radius 2 and 3. Raising them reduces the chance that the filter will touch fine detail or text; lowering them makes the correction more aggressive. Recent tuning shows that moderate increases (e.g. 0.5–1.0) often improve legibility of on-screen text while still cleaning natural edges.
Hard-edged text and natural image content (skin, hair, fabric) respond differently. When text must remain readable, raise pr2/pr3 and/or knee; when only natural areas matter, lower protection is usually fine. A single global setting is always a compromise.
Actually, this tool has dynamic function and works on all parts of the frame that are prone to ringing or to thin echo lines.
Different scenes and different artifacts will ideally require different settings, so you want to find some averaged balance.
Properly tweaked, many videos will look remarkably cleaner and more brilliant.
Also, file-size can be reduced, when there are less artifacts in the signal.
When your workflow does some filtering, you may run into sub-pixel issues. If you can't get the offset right, you can try to resample to a higher resolution and apply the function there, with a higher offset.
Known limitations:
Best on vertical edges; horizontal ringing is only partially addressed.
In general, use the latest version, but in case you struggle with processing power, you can try earlier versions that e.g. work on half-side, half-wave.
Not a substitute for proper deinterlacing or pre-blend removal of sharpening of interlaced source.
The current version cannot easily target 4k formats, because in DVDs there is a tight relationship between resolution, size of shown structures and textures, and technical means to clean up the signal, each and every pixel counts.
For 4k operation, the windowing and autocorrelation functions would need to be rewritten to detect artifacts and shapes of a much bigger size.
Only the latest version is recommended for best results. Older versions may deliver only partial operation that uses less CPU.
Example:
* read data with MPEG2Source (),
* convert to 12-bit planar, whatever Expr() and other functions you are using can handle,
* also resize to 960x640 or higher)
* do some sharpening, preferably with linear convolution, the impulse may be asymmetric.
* at that point you can insert our function, be aware you may have to declare the data format.
last = AntiRing22LR(last, "luma", weightL=0.2,offsetL=3,weightR=0.2,offsetR=4,knee=0.3,pr2=0.5,pr3=0.5)
The "output" parameter when specified, is either a format string, or the literal "out=in" (try this in case of error messages), within the limits of Expr()
It was tested with a recent "avisynth plus 64bit" version, please make sure that you run a compatible version.
Thanks, please enjoy, have fun, have success!
earliest version:
function AntiRingLR(clip cl, string "planes", string "output", float "weightL", int "offsetL", float "weightR", int "offsetR", float "knee", float "pr2", float "pr3")
{ # --- Hans' Ringing Remover ---
weightL = Default(weightL, 0.25) # 0.15 .. 0.7, left intensity; 0.0 = inactive
offsetL = Default(offsetL,1) # 0..5, distance from an edge
weightR = Default(weightR, 0.25) # right side
offsetR = Default(offsetR,1)
knee = Default(knee,0.7) # 0.2 .. 1.5 lower = softer, more linear. higher = more on-off like, useful for printed text
pr2 = Default(pr2,0.5) # 0.0 .. 1.5 protection for signal components with radius=2
pr3 = Default(pr3,0.5) # 0.0 .. 1.5 protection for signal components with radius=3
# the higher you set the pr parameters, the less often the artifact remover will get triggered. it depends on the content of the frame.
#
# you invoke the function for one particular distance of an artifact to an edge, left, or right, or both.
# you can invoke it again with a different offset, if there is more than one strong artifact on the same side, but the instances may interact in unexpected ways.
expr = "x[2,0] x[1,0] - dup * .75 * x[1,0] x - dup * + x x[-1,0] - dup * + x[-1,0] x[-2,0] - dup * .75 * + sqrt _F1@ " +\
"x[3,0] x[1,0] - dup * .66 * x[2,0] x[0,0] - dup * + x[1,0] x[-1,0] - dup * + x[0,0] x[-2,0] - dup * .66 * + sqrt " + String(pr2*0.36) + " * - " +\
"x[3,0] x[0,0] - dup * .5 * x[2,0] x[-1,0] - dup * + x[1,0] x[-2,0] - dup * + x[0,0] x[-3,0] - dup * .66 * + sqrt " + String(pr3*0.3) + " * - " +\
String(10.0/knee) + " / " + String(0.5*knee) + " - 0.0 max dup 1.0 - swap 1.0 + / 1.0 + 128.0 * 255.0 min " +\
"x["+ String(offsetL) +",0] x["+ String(2+offsetL) +",0] - 256.0 / * "+ String(weightL) +" * -64.0 max 127.0 min " +\
" _F1 " +\
"x[-3,0] x[-1,0] - dup * .66 * x[-2,0] x[0,0] - dup * + x[-1,0] x[1,0] - dup * + x[0,0] x[2,0] - dup * .66 * + sqrt " + String(pr2*0.36) + " * - " +\
"x[-3,0] x[-0,0] - dup * .5 * x[-2,0] x[1,0] - dup * + x[-1,0] x[2,0] - dup * + x[0,0] x[3,0] - dup * .66 * + sqrt " + String(pr3*0.3) + " * - " +\
String(10.0/knee) + " / " + String(0.5*knee) + " - 0.0 max dup 1.0 - swap 1.0 + / 1.0 + 128.0 * 255.0 min " +\
"x["+ String(-offsetR) +",0] x["+ String(-2-offsetR) +",0] - 256.0 / * "+ String(weightR) +" * -64.0 max 127.0 min + x + "
planes = Default(planes, "luma")
Y = (planes == "luma" || planes == "all") ? 3 : 1
U = (planes == "chroma" || planes == "all") ? 3 : 1
V = (planes == "chroma" || planes == "all") ? 3 : 1
output = Default(output, "YUV444P12")
output = (output=="out=in") ? "" : output
if (output=="") {
return cl.Expr( (Y==3 ? expr : "x"), (U==3 ? expr : "x"), (V==3 ? expr : "x"), scale_inputs = "allf")
} else {
return cl.Expr( (Y==3 ? expr : "x"), (U==3 ? expr : "x"), (V==3 ? expr : "x"), scale_inputs = "allf", format=output)
}
}
Here is the most recent update from Aug, 2026: https://forum.doom9.org/showthread.php?p=2049482#post2049482 (improves pr2,pr3 functionality)
This function is recommended, when you remaster or recompress old DVDs and try some sharpening, eg. with convolutional sharpeners.
You can use this function freely, but please when you store it somewhere, add the line "Hans' ringing remover".
Technical functionality:
What it does, it analyzes the signal and predicts one artifact to the left and one to the right of a vertical edge.
Each predicted artifact has a positive and a negative lobe, mimicking the decay of a wave form (each is one sample only, relying on proper filtering).
You can add the function a second time to tackle ringing with longer tails.
The placement of the artifact is set by the offset parameters.
The predicted artifact thus is being reengineered, and then subtracted from the original.
In other words, you have to set the weight parameters, and monitor the effect on a particular echo or Gibbs ringing structure.
The task is to zero in weight, not too light, not too dark, to make the artifacts less visible.
It is entirely possible to use negative weight, because some processes in recording and video production may cause negative artifact polarity.
Quality:
The quality level is DVD-related, but regarding Avisynth script processing, it was configured and tested with planar formats like YUV444 and 12-bit resolution.
I think we need also some upsampling. I routinely go from DVD to 960x640, then finally dithered down to 8bit for the mpeg encoding. Good compromise between quality and disk space.
Anti-ringing needs some finer granularity, I would not recommend to do the math at 8bit 720x480. YMMV.
Using the function at higher than double-size upsampling also is not recommended. Operating at 4k level would require to include wider signal windows and quadruple amount of autocorrelation/FIR signal taps (feel free to implement).
It has been tested successfully at 12-bit, 720x480, 960x640, 1500x960 (exact size does not matter but should be mod 4).
You can tackle different artifacts at different upsizing levels, take what fits best.
The correction should be applied on the Luma signal, but you can experiment as you like.
Hints for operation:
Find the dominant ringing distance first (change offset while watching a strong edge).
This means you look at the pixel distance between edge and artifact, and set offset to 1 .. 7, depending on which one is the most disturbing of several echoes.
In some cases, also in upsampled mode, offset values of 0 or even 5 to 7 may fit the issue. Negative offset is experimental, eg. for special effects.
Expect to run one or two instances with different offsets, and also at different resampling levels.
Tune in on a strongly visible artifact that is repetitive through the video. Start with a weight between 0.5 and 1.0, then try out offsets.
Great when you find out you can reduce weight values after getting the offsets right.
In short: Find the right offset, then nudge the weight of the corrective signal.
Negative weight can be used as well, because in video recording and processing, artifacts can come with switched polarity.
It may be rewarding to let some subtle remainder of ringing artifacts be handled by the x264 denoiser and not try to cancel out everything exactly.
Dynamic, protective operation:
The algorithm seeks to avoid blurring and also messing-up of small shapes in the image.
For this, pr2 and pr3 control protection of local signal structures at radius 2 and 3. Raising them reduces the chance that the filter will touch fine detail or text; lowering them makes the correction more aggressive. Recent tuning shows that moderate increases (e.g. 0.5–1.0) often improve legibility of on-screen text while still cleaning natural edges.
Hard-edged text and natural image content (skin, hair, fabric) respond differently. When text must remain readable, raise pr2/pr3 and/or knee; when only natural areas matter, lower protection is usually fine. A single global setting is always a compromise.
Actually, this tool has dynamic function and works on all parts of the frame that are prone to ringing or to thin echo lines.
Different scenes and different artifacts will ideally require different settings, so you want to find some averaged balance.
Properly tweaked, many videos will look remarkably cleaner and more brilliant.
Also, file-size can be reduced, when there are less artifacts in the signal.
When your workflow does some filtering, you may run into sub-pixel issues. If you can't get the offset right, you can try to resample to a higher resolution and apply the function there, with a higher offset.
Known limitations:
Best on vertical edges; horizontal ringing is only partially addressed.
In general, use the latest version, but in case you struggle with processing power, you can try earlier versions that e.g. work on half-side, half-wave.
Not a substitute for proper deinterlacing or pre-blend removal of sharpening of interlaced source.
The current version cannot easily target 4k formats, because in DVDs there is a tight relationship between resolution, size of shown structures and textures, and technical means to clean up the signal, each and every pixel counts.
For 4k operation, the windowing and autocorrelation functions would need to be rewritten to detect artifacts and shapes of a much bigger size.
Only the latest version is recommended for best results. Older versions may deliver only partial operation that uses less CPU.
Example:
* read data with MPEG2Source (),
* convert to 12-bit planar, whatever Expr() and other functions you are using can handle,
* also resize to 960x640 or higher)
* do some sharpening, preferably with linear convolution, the impulse may be asymmetric.
* at that point you can insert our function, be aware you may have to declare the data format.
last = AntiRing22LR(last, "luma", weightL=0.2,offsetL=3,weightR=0.2,offsetR=4,knee=0.3,pr2=0.5,pr3=0.5)
The "output" parameter when specified, is either a format string, or the literal "out=in" (try this in case of error messages), within the limits of Expr()
It was tested with a recent "avisynth plus 64bit" version, please make sure that you run a compatible version.
Thanks, please enjoy, have fun, have success!
earliest version:
function AntiRingLR(clip cl, string "planes", string "output", float "weightL", int "offsetL", float "weightR", int "offsetR", float "knee", float "pr2", float "pr3")
{ # --- Hans' Ringing Remover ---
weightL = Default(weightL, 0.25) # 0.15 .. 0.7, left intensity; 0.0 = inactive
offsetL = Default(offsetL,1) # 0..5, distance from an edge
weightR = Default(weightR, 0.25) # right side
offsetR = Default(offsetR,1)
knee = Default(knee,0.7) # 0.2 .. 1.5 lower = softer, more linear. higher = more on-off like, useful for printed text
pr2 = Default(pr2,0.5) # 0.0 .. 1.5 protection for signal components with radius=2
pr3 = Default(pr3,0.5) # 0.0 .. 1.5 protection for signal components with radius=3
# the higher you set the pr parameters, the less often the artifact remover will get triggered. it depends on the content of the frame.
#
# you invoke the function for one particular distance of an artifact to an edge, left, or right, or both.
# you can invoke it again with a different offset, if there is more than one strong artifact on the same side, but the instances may interact in unexpected ways.
expr = "x[2,0] x[1,0] - dup * .75 * x[1,0] x - dup * + x x[-1,0] - dup * + x[-1,0] x[-2,0] - dup * .75 * + sqrt _F1@ " +\
"x[3,0] x[1,0] - dup * .66 * x[2,0] x[0,0] - dup * + x[1,0] x[-1,0] - dup * + x[0,0] x[-2,0] - dup * .66 * + sqrt " + String(pr2*0.36) + " * - " +\
"x[3,0] x[0,0] - dup * .5 * x[2,0] x[-1,0] - dup * + x[1,0] x[-2,0] - dup * + x[0,0] x[-3,0] - dup * .66 * + sqrt " + String(pr3*0.3) + " * - " +\
String(10.0/knee) + " / " + String(0.5*knee) + " - 0.0 max dup 1.0 - swap 1.0 + / 1.0 + 128.0 * 255.0 min " +\
"x["+ String(offsetL) +",0] x["+ String(2+offsetL) +",0] - 256.0 / * "+ String(weightL) +" * -64.0 max 127.0 min " +\
" _F1 " +\
"x[-3,0] x[-1,0] - dup * .66 * x[-2,0] x[0,0] - dup * + x[-1,0] x[1,0] - dup * + x[0,0] x[2,0] - dup * .66 * + sqrt " + String(pr2*0.36) + " * - " +\
"x[-3,0] x[-0,0] - dup * .5 * x[-2,0] x[1,0] - dup * + x[-1,0] x[2,0] - dup * + x[0,0] x[3,0] - dup * .66 * + sqrt " + String(pr3*0.3) + " * - " +\
String(10.0/knee) + " / " + String(0.5*knee) + " - 0.0 max dup 1.0 - swap 1.0 + / 1.0 + 128.0 * 255.0 min " +\
"x["+ String(-offsetR) +",0] x["+ String(-2-offsetR) +",0] - 256.0 / * "+ String(weightR) +" * -64.0 max 127.0 min + x + "
planes = Default(planes, "luma")
Y = (planes == "luma" || planes == "all") ? 3 : 1
U = (planes == "chroma" || planes == "all") ? 3 : 1
V = (planes == "chroma" || planes == "all") ? 3 : 1
output = Default(output, "YUV444P12")
output = (output=="out=in") ? "" : output
if (output=="") {
return cl.Expr( (Y==3 ? expr : "x"), (U==3 ? expr : "x"), (V==3 ? expr : "x"), scale_inputs = "allf")
} else {
return cl.Expr( (Y==3 ? expr : "x"), (U==3 ? expr : "x"), (V==3 ? expr : "x"), scale_inputs = "allf", format=output)
}
}