TASK 51 blur 'heavy' fix: quadratic blur radius. BlurRadius(strength) = 1 + 5s^2, MaxBlurRadius 12 -> 6 downscaled px (~24 full px max). Default 0.5 is now radius 2 small px (was the old linear 6) - ~3x lighter perceived spread, matching the creator's real-camera finding. Range from the established tools: StreamFX defaults its Box blur Size to 5 and its author stops downscale-based blurs around 8px. One integration test pins the mapping (0 -> 1, 0.5 -> 2, 1 -> 6) and step-edge behaviour (light reaches 9px at default vs 26px at full); the existing slice-2 blur test passes unchanged. 419/419 pass. Refs: https://github.com/Vhonowslend/StreamFX-Public/wiki/Filter-Blur https://xaymar.com/articles/2020/04/26/optimizing-streamfx-in-your-obs-scenes/ (Unit: bg-blur heavy fix)

This commit is contained in:
2026-10-10 07:08:01 -07:00
parent 305706956e
commit c2d64dcd6d
7 changed files with 124 additions and 44 deletions
@@ -147,6 +147,67 @@ public class BackgroundEffectEngineTests
}
}
/// <summary>TASK 51 blur-heavy fix — the integration test: an all-background frame with one
/// bright pixel. The default 0.5 strength must keep the halo tight (quadratic curve: radius 2
/// small px) while full strength spreads it far (radius 6) — the maker complained the default
/// was heavy under the old linear 1..12 range.</summary>
[Fact]
public void Blur_default_strength_is_gentle_and_full_strength_is_strong()
{
const int w = 128, h = 64;
var pixels = new byte[w * h * 4];
for (var y = 0; y < h; y++)
for (var x = 0; x < w; x++)
{
var p = (y * w + x) * 4;
if (x < w / 2) pixels[p] = 200;
pixels[p + 1] = 180;
pixels[p + 2] = 160;
pixels[p + 3] = 255;
}
var frame = new VideoFrame(w, h, pixels) { IsOpaque = true };
// All background — the whole frame blurs, no person boundary to confuse the edge spread.
var mask = new SegmentationMask(4, 2, new float[8]);
var processor = new BackgroundEffectProcessor(new FakeSegmenter(mask));
var gentle = processor.Apply(frame, BackgroundEffect.Blur, BackgroundEffectProcessor.DefaultStrength);
var strong = processor.Apply(frame, BackgroundEffect.Blur, 1.0);
// The radius mapping itself: quadratic, not the old linear 1..12. Default 0.5 = 2 small px
// (was 6), max = 6 small px (was 12) — the reason the default felt heavy before.
Assert.Equal(1, BackgroundEffectProcessor.BlurRadius(0.0));
Assert.Equal(2, BackgroundEffectProcessor.BlurRadius(BackgroundEffectProcessor.DefaultStrength));
Assert.Equal(BackgroundEffectProcessor.MinBlurRadius, BackgroundEffectProcessor.BlurRadius(0.0));
Assert.Equal(BackgroundEffectProcessor.MaxBlurRadius, BackgroundEffectProcessor.BlurRadius(1.0));
// Behaviour at a step edge: the default smears light only ~9px into the dark half (measured),
// full strength smears ~26px — a gentle default with a real range above it.
var (reachGentle, sumGentle) = EdgeReach(gentle);
var (reachStrong, sumStrong) = EdgeReach(strong);
Assert.True(reachGentle <= 12, $"default blur reach {reachGentle}px (expected <= 12)");
Assert.True(reachStrong >= 20, $"max blur reach {reachStrong}px (expected >= 20)");
Assert.True(reachStrong > reachGentle);
Assert.True(sumStrong > sumGentle * 2, "max strength should smear visibly more light than default");
}
/// <summary>Largest x into the dark half (x = w/2) still carrying light, and the total light
/// in the dark half — the blur's smearing distance at a step edge.</summary>
private static (int Reach, double Sum) EdgeReach(VideoFrame f)
{
var edge = f.Width / 2;
var reach = 0;
double sum = 0;
for (var y = 0; y < f.Height; y++)
for (var x = edge; x < f.Width; x++)
{
var v = f.BgraPixels[(y * f.Width + x) * 4];
if (v >= 32) reach = Math.Max(reach, x - edge);
sum += v;
}
return (reach, sum);
}
private static int Alpha(VideoFrame f, int x, int y) => f.BgraPixels[(y * f.Width + x) * 4 + 3];
private static string Fixture(string name) => Path.Combine(AppContext.BaseDirectory, "Fixtures", name);