using System;
using System.Buffers;
namespace ytLive.Services.Compositor;
///
/// The zoom box's magnifier (creator-corrected model, 2026-10-08): the box is a
/// detached 2x view of a FIXED capture window — the box/2 region centred on the
/// spot the creator clicked — sampled from the ALREADY-COMPOSITED frame (every
/// layer: live backdrop, art, webcam, sources, alerts). Moving the box relocates
/// it on screen without changing the capture; the box is painted LAST (layer n+1)
/// so it sits above everything. The capture snapshot is taken before the box is
/// drawn, so a box overlapping its own source never recurses.
///
internal static class ZoomMagnifier
{
///
/// Magnify a box/2 capture window onto a box/2 ² box in
/// (an opaque, already-composited crop-space buffer). dst pixel centres map to
/// capture coords as cap + (dstLocal + 0.5) / 2 — the exact 2x the preview's
/// VisualBrush shows via a box/2 Viewbox with Stretch=Fill.
/// clips the box to a true circle (zoom shape Circle).
///
internal static void MagnifyInto(
byte[] dst, int dstW, int dstH,
double boxX, double boxY, double boxW, double boxH,
double capX, double capY,
bool round)
{
if (boxW <= 0 || boxH <= 0) return;
if (dstW <= 0 || dstH <= 0) return;
// Integer box draw bounds (clamped to the buffer).
var bx0 = Math.Max(0, (int)Math.Floor(boxX));
var by0 = Math.Max(0, (int)Math.Floor(boxY));
var bx1 = Math.Min(dstW, (int)Math.Ceiling(boxX + boxW));
var by1 = Math.Min(dstH, (int)Math.Ceiling(boxY + boxH));
if (bx0 >= bx1 || by0 >= by1) return;
var bw = bx1 - bx0;
var bh = by1 - by0;
// Integer capture-window read bounds (the 2x window is box/2, clamped).
var rx0 = Math.Max(0, (int)Math.Floor(capX));
var ry0 = Math.Max(0, (int)Math.Floor(capY));
var rx1 = Math.Min(dstW, (int)Math.Ceiling(capX + boxW / 2.0));
var ry1 = Math.Min(dstH, (int)Math.Ceiling(capY + boxH / 2.0));
var rw = rx1 - rx0;
var rh = ry1 - ry0;
if (rw <= 0 || rh <= 0) return;
// Snapshot the capture first: the box may overlap its own source region.
var src = ArrayPool.Shared.Rent(rw * rh * 4);
try
{
for (var y = 0; y < rh; y++)
Buffer.BlockCopy(dst, ((ry0 + y) * dstW + rx0) * 4, src, y * rw * 4, rw * 4);
var radius = Math.Min(boxW, boxH) / 2.0;
var ccx = boxX + boxW / 2.0;
var ccy = boxY + boxH / 2.0;
for (var y = 0; y < bh; y++)
{
var dy = by0 + y;
var sy = capY + (dy + 0.5 - boxY) * 0.5 - ry0;
if (sy < 0.0) sy = 0.0;
if (sy > rh - 1.0) sy = rh - 1.0;
var syi = (int)sy;
var syInv = Math.Min(syi + 1, rh - 1);
var fy8 = (int)((sy - syi) * 256);
var fyInv = 256 - fy8;
var di = (dy * dstW + bx0) * 4;
for (var x = 0; x < bw; x++, di += 4)
{
var dx = bx0 + x;
if (round)
{
var ox = dx - ccx;
var oy = dy - ccy;
if (ox * ox + oy * oy > radius * radius) continue;
}
var sx = capX + (dx + 0.5 - boxX) * 0.5 - rx0;
if (sx < 0.0) sx = 0.0;
if (sx > rw - 1.0) sx = rw - 1.0;
var sxi = (int)sx;
var sxInv = Math.Min(sxi + 1, rw - 1);
var fx8 = (int)((sx - sxi) * 256);
var fxInv = 256 - fx8;
var p00 = (syi * rw + sxi) * 4;
var p10 = (syi * rw + sxInv) * 4;
var p01 = (syInv * rw + sxi) * 4;
var p11 = (syInv * rw + sxInv) * 4;
dst[di] = (byte)Bilinear(src[p00], src[p10], src[p01], src[p11], fx8, fxInv, fy8, fyInv);
dst[di + 1] = (byte)Bilinear(src[p00 + 1], src[p10 + 1], src[p01 + 1], src[p11 + 1], fx8, fxInv, fy8, fyInv);
dst[di + 2] = (byte)Bilinear(src[p00 + 2], src[p10 + 2], src[p01 + 2], src[p11 + 2], fx8, fxInv, fy8, fyInv);
dst[di + 3] = 255;
}
}
}
finally
{
ArrayPool.Shared.Return(src);
}
}
/// Two-stage bilinear in 8.8 fixed point (same precision as
/// 's sampler).
private static int Bilinear(int tl, int tr, int bl, int br, int fx8, int fxInv, int fy8, int fyInv)
{
var top = tl * fxInv + tr * fx8;
var bot = bl * fxInv + br * fx8;
return (top * fyInv + bot * fy8 + 32768) >> 16;
}
}