Zoom: dims never shade the box — move the dim overlay above CanvasGrid (Unit: zoom shading fix)
Creator's final zoom correction: as the box is dragged, its capture region slides under the dim rectangles and the box picks up the 55% black "div shading" inside itself, because the VisualBrush samples the whole CanvasGrid and DimRects lived inside CanvasGrid. The captured area must stay clear regardless of box position. - PreviewPane.xaml: the DimRects ItemsControl is now a SIBLING of CanvasGrid (above the composed content, below the ZoomBox), never a descendant — the zoom source is shading-free by construction. - ZoomBoxInteractionTests: structural invariant — DimRectsLayer is a direct child of PreviewCanvas (not inside CanvasGrid) and renders below the ZoomBox. - Side effect (correctness, not a bug): quality dims now also sedate the global overlays outside the crop, matching "chrome outside the crop is dimmed".
This commit is contained in:
+27
-20
@@ -316,26 +316,6 @@
|
||||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-13,-13"/>
|
||||
</Grid>
|
||||
|
||||
<ItemsControl ItemsSource="{Binding DimRects}" IsHitTestVisible="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}"/>
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Rectangle Width="{Binding Width}" Height="{Binding Height}"
|
||||
Fill="#000000" Opacity="0.55"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Rectangle IsHitTestVisible="False" Stroke="#e94560" StrokeThickness="3"
|
||||
Canvas.Left="{Binding OutputRectX}" Canvas.Top="{Binding OutputRectY}"
|
||||
Width="{Binding OutputRectWidth}" Height="{Binding OutputRectHeight}"
|
||||
@@ -410,6 +390,33 @@
|
||||
</Canvas>
|
||||
</Grid>
|
||||
|
||||
<!-- The zoom dims: preview chrome ONLY — a SIBLING of CanvasGrid,
|
||||
never a descendant — so the zoom box's VisualBrush sample of
|
||||
CanvasGrid can never pick up the dim shading. The box must show
|
||||
the captured area clean at all times, even when dragged over the
|
||||
dims (creator ruling 2026-10-08). Renders above the composed
|
||||
content (dims the whole scene outside the box incl. the global
|
||||
overlays) and below the ZoomBox. -->
|
||||
<ItemsControl x:Name="DimRectsLayer" ItemsSource="{Binding DimRects}" IsHitTestVisible="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Canvas/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style TargetType="ContentPresenter">
|
||||
<Setter Property="Canvas.Left" Value="{Binding X}"/>
|
||||
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Rectangle Width="{Binding Width}" Height="{Binding Height}"
|
||||
Fill="#000000" Opacity="0.55"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- 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
|
||||
|
||||
+25
-35
@@ -1,4 +1,4 @@
|
||||
# HANDOFF — 2026-10-08 (zoom rework)
|
||||
# HANDOFF — 2026-10-08 (zoom rework + dims-shading fix)
|
||||
|
||||
## Branch
|
||||
- main (no feature branch)
|
||||
@@ -9,51 +9,41 @@
|
||||
- `Controls/LeftPanel.xaml` (pinned-message UI)
|
||||
- Untracked: `Helpers/RelayCommandGeneric.cs`
|
||||
|
||||
## What just happened (2026-10-08, second zoom pass — creator correction)
|
||||
The zoom box shipped in `4b3d82f` was a **loupe** (box follows its own content, built from the
|
||||
backdrop only, slotted at z-index 1). The creator corrected it; the model is now:
|
||||
## What just happened (2026-10-08, third zoom pass — dims shading)
|
||||
The zoom box shipped as a **loupe** (`4b3d82f`), then was corrected to an **anchored-capture
|
||||
magnifier over the full composite drawn last** (commit `6bff21b`). The final correction: as the box
|
||||
is dragged, the dim rectangles (55% black "div shading" around the box) were sliding under the
|
||||
capture region — shown inside the box because its `VisualBrush` samples all of `CanvasGrid`, and the
|
||||
`DimRects` ItemsControl previously lived INSIDE `CanvasGrid`.
|
||||
|
||||
- **Anchored capture, not a loupe.** Right-clicking the live preview fixes a **capture window** —
|
||||
the box/2 region centred on the click (`CaptureX/Y`, clamped to the master) — and parks a movable
|
||||
**2x magnifier** box. The box is a **live, always-on 2x view of that captured region**; dragging the
|
||||
box relocates it, **`UpdateZoomMove` never touches the capture** (creator ruling).
|
||||
- **Content = every layer.** The zoom pass runs on the **already-composited frame** (backdrop + all
|
||||
elements), so the magnified webcam/square/layers all show in the box. `SceneCompositor` skips the
|
||||
active `ZoomSceneElement` in every layer loop and `BlitZoom`s it **LAST (layer n+1, on top of
|
||||
everything)** via the pure static `ZoomMagnifier` (2x bilinear, 8.8 fixed-point, temp snapshot via
|
||||
`ArrayPool` so a box overlapping its own source never recurses, optional round clip).
|
||||
- **Rectangle = 4:3** ("more of a 640x480 aspect ratio"); Circle = square, Phone = 9:16 (kept).
|
||||
|
||||
Changes in this pass: new `Services/Compositor/ZoomMagnifier.cs`; `ZoomSceneElement`/`ZoomState`
|
||||
gained `CaptureX`/`CaptureY`; `MainViewModel.Zoom.cs` rewritten (`BuildZoomFrame`/`ZoomFrameBuilder`
|
||||
and the zoom-as-crop resolver case removed — no per-frame builder/cache anymore);
|
||||
`PreviewPane.xaml`: `ZoomBox` hoisted OUT of `OverlayCanvas` to be a **sibling of `CanvasGrid`**
|
||||
(renders above everything in the preview); `PreviewPane.xaml.cs` `UpdateZoomBox` samples `CanvasGrid`
|
||||
(the whole composed preview) at the fixed capture, `Stretch=Fill`. `ZoomFrameBuilderTests.cs` deleted
|
||||
(superseded); new `ZoomMagnifierTests.cs` (the "box over blue shows green capture, at 2x, all layers"
|
||||
proof + round-clip); `ZoomBoxInteractionTests.cs` rewritten (per-shape 4:3/9:16/square + capture
|
||||
anchor centred on the click + move-moves-only-the-box assertion + corner-clamp).
|
||||
Fix: the `DimRects` overlay is now a **sibling of `CanvasGrid`** (renders above the composed
|
||||
content, below the `ZoomBox`), never a descendant — so the zoom sample can never pick up the shading
|
||||
and the captured area stays clear no matter where the box is dragged. Behavior consequence to note:
|
||||
in non-zoom ("quality dims") mode the dims now also shade the global overlays (brand flash, alert
|
||||
ticker, social bar) that sit above them in the old tree — consistent with "chrome outside the crop
|
||||
is dimmed". Structural invariant added to `ZoomBoxInteractionTests`: `DimRectsLayer` is a direct
|
||||
child of `PreviewCanvas`, not inside `CanvasGrid`, and renders below the `ZoomBox`.
|
||||
|
||||
## Zoom feature state
|
||||
- Models: `ZoomState.cs`, `ZoomShape.cs` (Circle/VerticalRectangle/HorizontalRectangle), `ZoomSceneElement.cs`.
|
||||
- ViewModel (`MainViewModel.Zoom.cs`): `ActivateZoom`/`ToggleZoom`/`DismissZoom`/`StartZoomMove`/`UpdateZoomMove`/`EndZoomMove` + `RebuildDimRectsForZoom`; transient element healed/mirrored by `HealZoomLayers`/`EnsureZoomLayer`.
|
||||
- Output: box composited last via `BlitZoom`/`ZoomMagnifier`; output crop = tier (box never the crop).
|
||||
- Preview: `ZoomBox` = sibling above `CanvasGrid`, samples the full composite at the fixed capture, 2x; circle→ellipse clip, rect/square outline per shape; `LeftPanel` hides the placeholder row via `IsZoomLayer`.
|
||||
- Preview: `ZoomBox` + `DimRectsLayer` are both SIBLINGS of `CanvasGrid` (box above dims above content); box samples the full composite at the fixed capture, 2x; circle→ellipse clip, rect/square outline per shape; `LeftPanel` hides the placeholder row via `IsZoomLayer`.
|
||||
- Rectangle shape = 4:3; Circle square; Phone 9:16. Capture anchored on click (`CaptureX/Y`); dragging the box never changes it.
|
||||
|
||||
## Tests
|
||||
- Full suite: **404 passed / 2 failed** (both = known pre-existing environment flakes, unrelated to
|
||||
zoom): `AudioPipelineTests.Mix_HonorsProviderGains_AndGameMute_KillsTheLoopback` (audio timing)
|
||||
and `LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder` (needs
|
||||
interactive desktop). Zoom tests 3/3 green, 0 build warnings.
|
||||
- Full suite: **406 passed / 0 failed** on a clean full run (the pre-existing audio-loopback timing
|
||||
flake and `RealMouseDrag`'s interactive-desktop requirement recur occasionally — not zoom
|
||||
regressions). Zoom tests 3/3 green, 0 build warnings.
|
||||
|
||||
## Known issues / follow-ups
|
||||
- **Background "Use default background" toggle still not working** (carried from 2026-10-07):
|
||||
binding exists (`UseDefaultBackground`), VM handler invalidates bake; investigate when time permits.
|
||||
- Preview shows editing chrome (dim rects, selection) inside the zoom box — accepted preview
|
||||
artefact; the output only shows composed content.
|
||||
- The 3px selection / crop-outline strokes could still show in the zoom box IF the capture overlaps
|
||||
them in edit mode — not shading, thin outlines only; the dims (the user's complaint) are gone.
|
||||
|
||||
## Next step
|
||||
- User runs the app live to eyeball the corrected zoom: right-click preview → anchored capture stays
|
||||
fixed while the box is dragged; the box shows ALL layers at 2x on top of everything; rectangle box
|
||||
is 4:3. Then TASK 51 (webcam background Remove + Blur) per `TASKS/task-51-webcam-bg-effects.md`,
|
||||
and the background-toggle investigation if still open.
|
||||
- User runs the app live to eyeball: drag the zoom box anywhere and the captured content stays clean
|
||||
(no dim shading in the box; the scene outside the box stays dimmed). Then TASK 51 (webcam
|
||||
background Remove + Blur) per `TASKS/task-51-webcam-bg-effects.md`, and the background-toggle
|
||||
investigation if still open.
|
||||
@@ -737,6 +737,11 @@ parallel software compositor** over the `VideoFrame` (BGRA8) seam, and the XAML
|
||||
renderers must agree: geometry, `UniformToFill` cover-crop, round clip, mirror, border, z-order. Preview
|
||||
stays XAML (editing view); the compositor is the output view.
|
||||
|
||||
- **The `DimRects` shading is itself preview chrome, so it renders as a SIBLING of `CanvasGrid`
|
||||
(above it, below the `ZoomBox`), never a descendant** (2026-10-08, creator ruling): the zoom box's
|
||||
`VisualBrush` samples all of `CanvasGrid`, and if the dims were inside it the box would show the
|
||||
dim shading — especially after the box is dragged away and the fixed capture slides under the dims.
|
||||
|
||||
- **Render the active tier's output rect directly** (`CompositorOptions {SourceRectX/Y/W/H,
|
||||
OutputWidth, OutputHeight}`, fed from `MainViewModel.OutputRect*`): 16:9 = full 1920×1080 1:1; the
|
||||
vertical 9:16 tier = composite the centered 607×1080 crop then bilinear-upscale to 1080×1920.
|
||||
|
||||
@@ -77,6 +77,19 @@ public sealed class ZoomBoxInteractionTests
|
||||
Assert.Equal(Visibility.Visible, circleOutline.Visibility);
|
||||
Assert.Equal(Visibility.Collapsed, rectOutline.Visibility);
|
||||
|
||||
// The dims are preview chrome ABOVE the composed content (a sibling of
|
||||
// CanvasGrid, below the ZoomBox) — never inside CanvasGrid — so the zoom
|
||||
// box's sample of CanvasGrid can never pick up the dim shading, even when
|
||||
// the zoom box is dragged so the capture slides under the dims.
|
||||
var previewCanvas = (Canvas)pane.FindName("PreviewCanvas")!;
|
||||
var canvasGrid = (Grid)pane.FindName("CanvasGrid")!;
|
||||
var dimsLayer = (ItemsControl)pane.FindName("DimRectsLayer")!;
|
||||
Assert.Equal(previewCanvas, VisualTreeHelper.GetParent(dimsLayer));
|
||||
for (var parent = VisualTreeHelper.GetParent(dimsLayer); parent != null; parent = VisualTreeHelper.GetParent(parent))
|
||||
Assert.False(ReferenceEquals(parent, canvasGrid), "the dims layer must not be inside CanvasGrid");
|
||||
Assert.True(previewCanvas.Children.IndexOf(dimsLayer) < previewCanvas.Children.IndexOf(zoomBox),
|
||||
"the dims must render below the zoom box, not above it");
|
||||
|
||||
// Phone (vertical rectangle): tall 9:16 box; capture centred on the click.
|
||||
vm.ZoomShape = ZoomShape.VerticalRectangle;
|
||||
vm.ActivateZoom(960, 540);
|
||||
|
||||
Reference in New Issue
Block a user