using System.IO; using System.Windows.Media; using System.Windows.Media.Imaging; using Xunit; using ytLive.Models; using ytLive.Services; using ytLive.Services.BackgroundEffects; namespace ytLive.Tests; /// TASK 51 slice 1 — the background-effects engine. public class BackgroundEffectEngineTests { private sealed class FakeSegmenter : IPersonSegmenter { private readonly SegmentationMask? _mask; public int Calls; public FakeSegmenter(SegmentationMask? mask) => _mask = mask; public SegmentationMask? Segment(VideoFrame frame) { Calls++; return _mask; } } /// The integration test for this unit: a webcam frame run through the /// processor with Remove comes back with the person kept and the background /// transparent, colors untouched, flagged non-opaque so the compositor blends it. /// None is a true passthrough (same instance, segmenter never called). [Fact] public void Remove_keeps_the_person_and_makes_the_background_transparent() { const int w = 64, h = 32; var pixels = new byte[w * h * 4]; for (var i = 0; i < pixels.Length; i += 4) { pixels[i] = 10; pixels[i + 1] = 20; pixels[i + 2] = 30; pixels[i + 3] = 255; } var frame = new VideoFrame(w, h, pixels) { IsOpaque = true }; // 4×2 mask: left half person, right half background. var mask = new SegmentationMask(4, 2, new[] { 1f, 1f, 0f, 0f, 1f, 1f, 0f, 0f }); var segmenter = new FakeSegmenter(mask); var processor = new BackgroundEffectProcessor(segmenter); Assert.Same(frame, processor.Apply(frame, BackgroundEffect.None)); Assert.Equal(0, segmenter.Calls); var result = processor.Apply(frame, BackgroundEffect.Remove); Assert.NotSame(frame, result); Assert.False(result.IsOpaque); Assert.Equal(w, result.Width); Assert.Equal(h, result.Height); for (var y = 0; y < h; y++) { Assert.Equal(255, Alpha(result, 4, y)); // well inside the person half Assert.Equal(0, Alpha(result, w - 5, y)); // well inside the background half } for (var i = 0; i < pixels.Length; i += 4) { Assert.Equal(pixels[i], result.BgraPixels[i]); Assert.Equal(pixels[i + 1], result.BgraPixels[i + 1]); Assert.Equal(pixels[i + 2], result.BgraPixels[i + 2]); } Assert.Equal(255, frame.BgraPixels[(w - 5) * 4 + 3]); // source frame never mutated } /// Model contract: the BUNDLED (embedded) ONNX model, driven through the C# /// preprocessing, reproduces Google's own MediaPipe runtime on a fixed fixture. /// The expected mask was produced by tools/selfie-seg/ running the original .tflite in /// MediaPipe (fixture: MediaPipe's public test image mozart_square.jpg, a public-domain /// painting, cropped to 256×144). Guards the embedding, the input layout (RGB, NHWC, /// /255) and the conversion itself. [Fact] public void Bundled_model_matches_the_MediaPipe_reference_mask() { var frame = LoadBgra(Fixture("selfie-fixture-256x144.png")); var expected = LoadBgra(Fixture("selfie-fixture-expected-mask.png")); using var segmenter = new OnnxPersonSegmenter(); var mask = segmenter.Segment(frame); Assert.NotNull(mask); Assert.Equal(OnnxPersonSegmenter.ModelWidth, mask!.Width); Assert.Equal(OnnxPersonSegmenter.ModelHeight, mask.Height); var agree = 0; var person = 0; for (var i = 0; i < mask.Width * mask.Height; i++) { var expectPerson = expected.BgraPixels[i * 4] > 127; var gotPerson = mask.Values[i] > 0.5f; if (expectPerson == gotPerson) agree++; if (gotPerson) person++; } var total = mask.Width * mask.Height; Assert.True(agree >= total * 0.995, $"binary agreement {100.0 * agree / total:0.00}% < 99.5%"); Assert.InRange(100.0 * person / total, 45, 60); // reference: 52.5% person } /// Slice 2 integration test: Blur keeps the person pixel-for-pixel and smooths the /// background — a 1px black/white checkerboard behind the "person" comes back as mid-tones, /// opaque, same size. [Fact] public void Blur_keeps_the_person_sharp_and_smooths_the_background() { const int w = 64, h = 32; 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] = 100; pixels[p + 1] = 150; pixels[p + 2] = 200; } else { var v = (byte)(((x + y) & 1) == 0 ? 0 : 255); pixels[p] = v; pixels[p + 1] = v; pixels[p + 2] = v; } pixels[p + 3] = 255; } var frame = new VideoFrame(w, h, pixels) { IsOpaque = true }; var mask = new SegmentationMask(4, 2, new[] { 1f, 1f, 0f, 0f, 1f, 1f, 0f, 0f }); var processor = new BackgroundEffectProcessor(new FakeSegmenter(mask)); var result = processor.Apply(frame, BackgroundEffect.Blur); Assert.NotSame(frame, result); Assert.True(result.IsOpaque); Assert.Equal(w, result.Width); Assert.Equal(h, result.Height); for (var y = 0; y < h; y++) { for (var x = 0; x < 24; x++) // person: untouched for (var c = 0; c < 4; c++) Assert.Equal(pixels[(y * w + x) * 4 + c], result.BgraPixels[(y * w + x) * 4 + c]); for (var x = 44; x < w; x++) // background: checkerboard smoothed away { var p = (y * w + x) * 4; for (var c = 0; c < 3; c++) Assert.InRange(result.BgraPixels[p + c], (byte)30, (byte)225); Assert.Equal(255, result.BgraPixels[p + 3]); } } } 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); private static VideoFrame LoadBgra(string path) { using var stream = File.OpenRead(path); var decoded = BitmapDecoder.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad).Frames[0]; var bgra = new FormatConvertedBitmap(decoded, PixelFormats.Bgra32, null, 0); var pixels = new byte[bgra.PixelWidth * bgra.PixelHeight * 4]; bgra.CopyPixels(pixels, bgra.PixelWidth * 4, 0); return new VideoFrame(bgra.PixelWidth, bgra.PixelHeight, pixels); } }