# 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