# Inpainting Engine (Metal)

## Description
Implement exemplar-based inpainting (Criminisi-style) using Metal for content-aware fill of masked regions.

## Acceptance Criteria
- [ ] `InpaintEngine` class with public interface:
  - `func inpaint(image: CGImage, mask: CGImage) async throws -> CGImage`
  - `func inpaintPreview(image: CGImage, mask: CGImage) async throws -> CGImage`
- [ ] Metal shaders in `Shaders.metal` for:
  - Mask dilation (2-4px configurable)
  - Mask feathering (gaussian blur on alpha)
  - Patch matching (find best match from known region)
  - Patch copying (fill unknown region)
  - Edge-aware blending (reduce seams)
- [ ] `PatchMatch.swift` implementing the algorithm:
  - Build image pyramid for preview vs export
  - Priority-based boundary pixel processing
  - Best-matching patch search
  - Boundary update after each patch copy
- [ ] Performance targets met:
  - Preview (2048px): < 300ms on A14
  - Export (12MP): < 4 seconds on A14
  - Export (48MP): < 12 seconds on A17 Pro
- [ ] Memory management:
  - Tile-based processing for images > 12MP
  - Peak memory < 1.5GB
  - Intermediate textures released aggressively
- [ ] Accelerate/vImage fallback when Metal unavailable
- [ ] Snapshot tests with reference images verifying output quality

## Technical Notes
- Criminisi algorithm paper: "Region Filling and Object Removal by Exemplar-Based Inpainting"
- Patch size typically 9x9 or 11x11 pixels
- Priority = confidence × data term (edge strength)
- Search region can be limited for performance
- Use MTLHeap for efficient texture allocation

## Edge Cases
- Metal unavailable: fall back to Accelerate with warning toast
- Memory pressure during export: throw error with "Image too large" message
- Very large mask (>50% of image): may produce poor results, warn user
- Mask touches image edge: handle boundary conditions in patch search
