The zoom feature shipped without a task record while its index rows carried the "TASK 51" label — which belongs to the webcam background effects (Remove + Blur). For an honest catalogue and handoff: - TASKS/task-52-zoom-box.md: feature provenance (creator-requested), the three 2026-10-08 corrections (loupe -> anchored capture over the full composite drawn last -> never show the dim shading), shapes, implementation map, tests, DONE. - TASKS.md: row 52 added (shipped); row 51 reworded — unblocked now that the zoom build shipped. - TASK 51 -> TASK 52 in Models/index.md, ViewModels/index.md, and the two code comments that pointed at the zoom as "TASK 51". - HANDOFF.md: pre-push readiness notes (5 commits ahead, push-safe).
48 lines
2.7 KiB
Markdown
48 lines
2.7 KiB
Markdown
# TASK 52 — Zoom box (anchored 2× magnifier)
|
||
|
||
**Status:** ✅ Done — shipped 2026-10-08 (three creator corrections during the build).
|
||
|
||
## What it is
|
||
Right-clicking the live preview parks a movable **2× magnifier box** that shows a
|
||
**live, always-on magnified view of a FIXED capture region** — the box/2 window centred
|
||
on the click. The captured area is whatever was under the click; it stays the same no
|
||
matter where the box is dragged.
|
||
|
||
## Creator rulings (order received, 2026-10-08)
|
||
1. **Not a loupe.** The click fixes the capture; **moving the box never changes its
|
||
content**. First cut shipped `4b3d82f` as a box-following loupe — reworked in
|
||
`6bff21b` to anchored `CaptureX/Y` (drag = `UpdateZoomMove` moves box only).
|
||
2. **Content = every layer.** The box magnifies the **fully-composited frame**
|
||
(backdrop + every element incl. webcam), drawn **LAST, on top of everything**
|
||
(layer n+1) — `SceneCompositor` skips the active `ZoomSceneElement` in every layer
|
||
loop and `BlitZoom`s it at the end via `Services/Compositor/ZoomMagnifier`.
|
||
3. **Never show shading.** The dim rectangles around the box must never appear inside
|
||
it — the `DimRects` overlay is a sibling of `CanvasGrid`, never a descendant, so the
|
||
zoom box's `VisualBrush` can't pick it up (`948dd08`).
|
||
4. **Rectangle = 4:3** ("more of a 640×480 aspect ratio"); Circle square, Phone 9:16.
|
||
|
||
## Shapes (master 1920×1080; max box 0.7·frame)
|
||
| Shape | Box | Notes |
|
||
|---|---|---|
|
||
| Circle | square, min(maxW,maxH) | round clip |
|
||
| VerticalRectangle (Phone) | 9:16 | h = 0.7·1080 |
|
||
| HorizontalRectangle (Tablet, default) | **4:3** | w = h·4/3 |
|
||
|
||
Box and capture are each centred on the click and clamped independently.
|
||
|
||
## Implementation
|
||
- `ViewModels/MainViewModel.Zoom.cs` — activate/move/dismiss + dims; transient
|
||
`ZoomSceneElement` healed/mirrored (`HealZoomLayers`/`EnsureZoomLayer`, z-index 1, never persisted).
|
||
- `Services/Compositor/ZoomMagnifier.cs` — pure 2× bilinear over an already-composited
|
||
opaque buffer (temp snapshot via `ArrayPool`, optional round clip).
|
||
- `Models/ZoomSceneElement.cs` / `Models/ZoomState.cs` — box rect + `CaptureX/Y`.
|
||
- `Controls/PreviewPane.xaml(.cs)` — `ZoomBox` (sibling above `CanvasGrid`, brushes the
|
||
whole composed preview at the fixed capture, 2×) + `DimRectsLayer` (sibling above the
|
||
content, below the box) + `UpdateZoomBox`.
|
||
- Output crop always equals the tier — the box is never the crop source.
|
||
|
||
## Tests
|
||
`ZoomMagnifierTests` (money proof: box over blue shows green capture at 2× incl. an extra
|
||
red "layer"; round-clip) + `ZoomBoxInteractionTests` (aspects, capture anchor centred on the
|
||
click, move-moves-only-the-box, corner clamp, dims-layer-structure invariant).
|
||
Full suite 406/406 on a clean run at `948dd08`. |