using System;
using Xunit;
using ytLive.Services.Compositor;
namespace ytLive.Tests;
///
/// The privacy blur (creator ruling 2026-11-01): a Live-scene layer that blurs the frame beneath it
/// inside a box or circle, so a creator can hide part of their desktop.
///
/// Two rules are load-bearing and both are here: pixels OUTSIDE the mask must be untouched
/// (otherwise the layer is a full-frame filter), and overlapping layers must resolve to the
/// strongest single blur, never to a blurred blur — the creator was explicit that a stack cannot
/// be stronger than any one slider setting.
///
public class RegionBlurTests
{
private const int W = 320;
private const int H = 180;
private const int Stride = W * 4;
/// A frame of vertical hard edges: blurring is then obvious (edges go grey).
private static byte[] StripedFrame()
{
var pixels = new byte[Stride * H];
for (var y = 0; y < H; y++)
for (var x = 0; x < W; x++)
{
var p = y * Stride + x * 4;
var on = (x / 8) % 2 == 0;
pixels[p] = pixels[p + 1] = pixels[p + 2] = (byte)(on ? 255 : 0);
pixels[p + 3] = 255;
}
return pixels;
}
private static int CenterVariance(byte[] pixels, int x0, int y0, int x1, int y1)
{
// Crude "is this still hard-edged" measure: count horizontal transitions in the middle row.
var y = (y0 + y1) / 2;
var transitions = 0;
for (var x = x0 + 1; x < x1; x++)
{
var a = pixels[y * Stride + (x - 1) * 4];
var b = pixels[y * Stride + x * 4];
if (Math.Abs(a - b) > 32) transitions++;
}
return transitions;
}
[Fact]
public void Pixels_outside_the_mask_are_untouched_and_inside_are_blurred()
{
var pixels = StripedFrame();
var before = (byte[])pixels.Clone();
var applied = RegionBlur.ApplyInPlace(
pixels, Stride, W, H, 100, 60, 120, 60, strength: 0.8, round: false);
Assert.True(applied);
// Outside: untouched, byte for byte.
for (var y = 0; y < H; y++)
for (var x = 0; x < W; x++)
{
var inside = x >= 100 && x < 220 && y >= 60 && y < 120;
if (inside) continue;
var p = y * Stride + x * 4;
Assert.Equal(before[p], pixels[p]);
}
// Inside: the hard stripes are gone.
Assert.True(CenterVariance(pixels, 110, 70, 210, 110) < CenterVariance(before, 110, 70, 210, 110));
}
[Fact]
public void A_round_blur_leaves_the_corners_of_its_box_alone()
{
var pixels = StripedFrame();
var before = (byte[])pixels.Clone();
RegionBlur.ApplyInPlace(pixels, Stride, W, H, 100, 60, 120, 60, strength: 0.8, round: true);
// The box's corner is outside the inscribed ellipse.
var corner = 60 * Stride + 100 * 4;
Assert.Equal(before[corner], pixels[corner]);
}
[Fact]
public void An_overlap_uses_the_strongest_blur_once_and_never_stacks()
{
// Same region blurred twice: without the coverage map the second pass blurs an already
// blurred region, which is stronger than either slider setting. With it, the second pass
// skips the covered pixels and the result equals a single strong pass.
var once = StripedFrame();
RegionBlur.ApplyInPlace(once, Stride, W, H, 100, 60, 120, 60, 0.8, false);
var onceVariance = CenterVariance(once, 110, 70, 210, 110);
var stacked = StripedFrame();
var coverage = new byte[W * H];
RegionBlur.ApplyInPlace(stacked, Stride, W, H, 100, 60, 120, 60, 0.8, false, coverage);
RegionBlur.ApplyInPlace(stacked, Stride, W, H, 100, 60, 120, 60, 0.8, false, coverage);
Assert.Equal(onceVariance, CenterVariance(stacked, 110, 70, 210, 110));
}
[Fact]
public void Strength_zero_still_blurs_a_little_and_the_curve_is_the_webcams()
{
// The shared radius curve: 1 downscaled px at 0, 6 at 1 — the webcam's own numbers.
Assert.Equal(RegionBlur.MinRadius, RegionBlur.RadiusFor(0));
Assert.Equal(RegionBlur.MaxRadius, RegionBlur.RadiusFor(1));
Assert.Equal(RegionBlur.MaxRadius, RegionBlur.RadiusFor(4)); // clamped
Assert.Equal(RegionBlur.MinRadius, RegionBlur.RadiusFor(-1));
}
}