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; } }