Zoom: anchor the capture, magnify the full composite, draw on top (Unit: zoom rework per creator correction)
Creator corrected the "loupe" zoom on 2026-10-08: the click anchors a fixed
capture region (box/2 centred on the click) and the box is a live 2x view of
that region — dragging the box never changes its content; it magnifies the
fully-composited frame (every layer, incl. webcam) and draws LAST (n+1).
Rectangle shape is 4:3 ("more of a 640x480 aspect").
- MainViewModel.Zoom.cs rewritten: ActivateZoom anchors CaptureX/CaptureY
(independently clamped); UpdateZoomMove relocates the box only; the per-frame
BuildZoomFrame/ZoomFrameBuilder path is gone.
- SceneCompositor: skips the active ZoomSceneElement in every layer loop and
BlitZooms it last via the new pure ZoomMagnifier (2x bilinear, temp snapshot
via ArrayPool, round clip).
- Preview: ZoomBox hoisted above CanvasGrid as a sibling; its brush samples the
whole composed preview at the fixed capture (2x).
- Tests: ZoomMagnifierTests (box-over-blue shows green capture at 2x incl. an
extra red "layer" — the loupe/backdrop-only regressions both fail it) +
round-clip; ZoomBoxInteractionTests rewritten (anchored capture, move-doesn't-
change-capture, 4:3/9:16/square). 404 pass, 2 known environment flakes.
This commit is contained in:
@@ -3,12 +3,15 @@ using System.Windows.Media;
|
||||
namespace ytLive.Models;
|
||||
|
||||
/// <summary>
|
||||
/// The transient zoom layer: a real scene element parked at z-index 1 (above the
|
||||
/// background, below every other layer) whose pixels are the 2x-magnified backdrop
|
||||
/// centred on the zoom box. The app paints it into the stream/recording exactly like
|
||||
/// any other layer. While inactive it is a 0x0 invisible placeholder that is never
|
||||
/// persisted (LayoutStore hand-rolls only Source/WebcamSceneConfig rows) — the VM
|
||||
/// re-inserts it on load and on scene creation, mirroring how backgrounds heal.
|
||||
/// The transient zoom layer. It is a real scene element, but a special one: while
|
||||
/// active the SceneCompositor draws it LAST (layer n+1, above everything) as a live
|
||||
/// 2x view of a FIXED capture window (the box/2 region centred on where the creator
|
||||
/// clicked), sampled from the just-composited frame — so it captures ALL layers and
|
||||
/// keeps showing the same content no matter where the box is dragged (creator ruling
|
||||
/// 2026-10-08). Its X/Y/W/H are the movable BOX placement; CaptureX/CaptureY is the
|
||||
/// anchored capture window. While inactive it is an invisible placeholder that is
|
||||
/// never persisted (LayoutStore hand-rolls only Source/WebcamSceneConfig rows) — the
|
||||
/// VM re-inserts it on load and on scene creation, mirroring how backgrounds heal.
|
||||
/// </summary>
|
||||
public sealed class ZoomSceneElement : SceneElement
|
||||
{
|
||||
@@ -16,13 +19,18 @@ public sealed class ZoomSceneElement : SceneElement
|
||||
/// the baked static base and composites per-frame.</summary>
|
||||
public bool IsZoomActive { get; set; }
|
||||
|
||||
/// <summary>Master-space top-left of the fixed capture window. Set once by
|
||||
/// <c>ActivateZoom</c>; moving the box must never touch it.</summary>
|
||||
public double CaptureX { get; set; }
|
||||
public double CaptureY { get; set; }
|
||||
|
||||
public override ElementKind Kind => IsZoomActive ? ElementKind.Dynamic : ElementKind.Static;
|
||||
|
||||
public override bool IsZoomLayer => true;
|
||||
|
||||
public override bool IsDraggable => false;
|
||||
|
||||
/// <summary>Never rendered by the element template — the preview magnifier
|
||||
/// (ZoomBox) and the output resolver own its pixels.</summary>
|
||||
/// <summary>Never rendered by the element template or the resolver — the preview
|
||||
/// magnifier (ZoomBox) and the output compositor's zoom pass own its pixels.</summary>
|
||||
public override ImageSource? DisplaySource => null;
|
||||
}
|
||||
Reference in New Issue
Block a user