Files
LlamaCasty/TASKS/task-52-zoom-box.md
T
gramps fe80e8c994 Docs: record the zoom box as TASK 52 and fix the task-numbering drift (Unit: zoom docs)
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).
2026-10-08 08:52:36 -07:00

48 lines
2.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.