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:
+19
-15
@@ -63,8 +63,8 @@
|
||||
</ContextMenu>
|
||||
</Grid.ContextMenu>
|
||||
<!-- Live desktop/game background (bottom layer; the static
|
||||
image renders above it). The zoom magnifier samples
|
||||
this layer — see UpdateZoomBox (TASK 51). -->
|
||||
image renders above it). The zoom box samples the
|
||||
whole CanvasGrid, not just this layer (TASK 51). -->
|
||||
<Grid x:Name="BackdropLayer" IsHitTestVisible="False">
|
||||
<Image Source="{Binding BackgroundImage}" Stretch="UniformToFill"
|
||||
Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}"/>
|
||||
@@ -72,19 +72,6 @@
|
||||
<Rectangle Stroke="#22c55e" StrokeThickness="6"/>
|
||||
</Grid>
|
||||
<Canvas x:Name="OverlayCanvas" Width="1920" Height="1080">
|
||||
<!-- The zoom box renders below every element layer so the
|
||||
preview shows exactly what the output composites (the
|
||||
zoom is a real layer at z-index 1, not an overlay). -->
|
||||
<Grid x:Name="ZoomBox" IsHitTestVisible="False"
|
||||
Canvas.Left="{Binding ZoomState.X}" Canvas.Top="{Binding ZoomState.Y}"
|
||||
Width="{Binding ZoomState.Width}" Height="{Binding ZoomState.Height}"
|
||||
Visibility="{Binding IsZoomActive, Converter={StaticResource BoolToVis}}">
|
||||
<Rectangle x:Name="ZoomRectOutline" Stroke="#d8a72b" StrokeThickness="3" Fill="Transparent"/>
|
||||
<Ellipse x:Name="ZoomCircleOutline" Stroke="#d8a72b" StrokeThickness="3" Fill="Transparent" Visibility="Collapsed"/>
|
||||
<Grid.Background>
|
||||
<VisualBrush x:Name="ZoomVisualBrush" ViewboxUnits="Absolute" Stretch="Fill"/>
|
||||
</Grid.Background>
|
||||
</Grid>
|
||||
<ItemsControl ItemsSource="{Binding StagedScene.Elements}"
|
||||
Width="1920" Height="1080">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@@ -422,6 +409,23 @@
|
||||
</Grid>
|
||||
</Canvas>
|
||||
</Grid>
|
||||
|
||||
<!-- The zoom box: layer n+1 — a SIBLING of CanvasGrid (not a
|
||||
descendant), so its VisualBrush can sample the whole
|
||||
composed preview (CanvasGrid = backdrop + every element
|
||||
layer) without a circular reference, and it renders ABOVE
|
||||
everything. It shows a fixed capture window (box/2 at the
|
||||
click); dragging the box relocates it, never the capture. -->
|
||||
<Grid x:Name="ZoomBox" IsHitTestVisible="False"
|
||||
Canvas.Left="{Binding ZoomState.X}" Canvas.Top="{Binding ZoomState.Y}"
|
||||
Width="{Binding ZoomState.Width}" Height="{Binding ZoomState.Height}"
|
||||
Visibility="{Binding IsZoomActive, Converter={StaticResource BoolToVis}}">
|
||||
<Rectangle x:Name="ZoomRectOutline" Stroke="#d8a72b" StrokeThickness="3" Fill="Transparent"/>
|
||||
<Ellipse x:Name="ZoomCircleOutline" Stroke="#d8a72b" StrokeThickness="3" Fill="Transparent" Visibility="Collapsed"/>
|
||||
<Grid.Background>
|
||||
<VisualBrush x:Name="ZoomVisualBrush" ViewboxUnits="Absolute" Stretch="Fill"/>
|
||||
</Grid.Background>
|
||||
</Grid>
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,8,0,0"
|
||||
|
||||
@@ -553,13 +553,15 @@ public partial class PreviewPane : UserControl
|
||||
if (DataContext is not MainViewModel vm) return;
|
||||
if (vm.IsZoomActive && ZoomVisualBrush != null)
|
||||
{
|
||||
// The preview mirrors the output zoom layer: sample the backdrop layer
|
||||
// (the live capture + art, exactly what the compositor magnifies) at the
|
||||
// box/2 interior centered on the box center → 2x on the screen.
|
||||
ZoomVisualBrush.Visual = BackdropLayer;
|
||||
// The preview mirrors the output zoom layer: sample the WHOLE composed
|
||||
// preview (CanvasGrid = backdrop + every element layer — the same all-layer
|
||||
// source the output compositor magnifies) at the FIXED capture window (the
|
||||
// box/2 region centred on the click) → 2x via Stretch=Fill. Moving the box
|
||||
// only moves the ZoomBox element; the Viewbox stays glued to the capture.
|
||||
ZoomVisualBrush.Visual = CanvasGrid;
|
||||
ZoomVisualBrush.Viewbox = new Rect(
|
||||
vm.ZoomState.X + vm.ZoomState.Width / 4,
|
||||
vm.ZoomState.Y + vm.ZoomState.Height / 4,
|
||||
vm.ZoomState.CaptureX,
|
||||
vm.ZoomState.CaptureY,
|
||||
vm.ZoomState.Width / 2,
|
||||
vm.ZoomState.Height / 2);
|
||||
ZoomVisualBrush.ViewboxUnits = BrushMappingMode.Absolute;
|
||||
@@ -576,7 +578,7 @@ public partial class PreviewPane : UserControl
|
||||
ZoomBox.Clip = null;
|
||||
}
|
||||
}
|
||||
else if (!vm.IsZoomActive && ZoomVisualBrush != null && ReferenceEquals(ZoomVisualBrush.Visual, BackdropLayer))
|
||||
else if (!vm.IsZoomActive && ZoomVisualBrush != null && ReferenceEquals(ZoomVisualBrush.Visual, CanvasGrid))
|
||||
{
|
||||
ZoomVisualBrush.Visual = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user