Commit graph

14 commits

Author SHA1 Message Date
88d04515e2 Extract StackBlur and split CameraScreen into smaller files
Move the ~220-line stack blur algorithm from ImageCaptureHandler into
its own util/StackBlur.kt object, making it independently testable
and reducing ImageCaptureHandler from 759 to 531 lines.

Split CameraScreen.kt (873 lines) by extracting:
- ControlPanel.kt: ModeToggle, ControlPanel, SliderControl
- CaptureControls.kt: CaptureButton, LastPhotoThumbnail

CameraScreen.kt is now 609 lines focused on layout and state wiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:50:30 +01:00
11a79076bc Fix concurrency, lifecycle, performance, and config issues from audit
Concurrency & bitmap lifecycle:
- Defer bitmap recycling by one cycle so Compose finishes drawing before
  native memory is freed (preview bitmaps, thumbnails)
- Make galleryPreviewSource @Volatile for cross-thread visibility
- Join preview job before recycling source bitmap in cancelGalleryPreview()
  to prevent use-after-free during CPU blur loop
- Add @Volatile to TiltShiftRenderer.currentTexCoords (UI/GL thread race)
- Fix error dismiss race with cancellable Job tracking

Lifecycle & resource management:
- Release GL resources via glSurfaceView.queueEvent (must run on GL thread)
- Pause GLSurfaceView when entering gallery preview mode
- Shut down captureExecutor in CameraManager.release() (thread leak)
- Use WeakReference for lifecycleOwnerRef to avoid Activity GC delay
- Fix thumbnail bitmap leak on coroutine cancellation (add to finally)
- Guarantee imageProxy.close() in finally block

Performance:
- Compute gradient mask at 1/4 resolution with bilinear upscale (~93%
  less per-pixel trig work, ~75% less mask memory)
- Precompute cos/sin on CPU, pass as uCosAngle/uSinAngle uniforms
  (eliminates per-fragment transcendental calls in GLSL)
- Unroll 9-tap Gaussian blur kernel (avoids integer-branched weight
  lookup that de-optimizes on mobile GPUs)
- Add 80ms debounce to preview recomputation during slider drags

Silent failure fixes:
- Check bitmap.compress() return value; report error on failure
- Log all loadBitmapFromUri null paths (stream, dimensions, decode)
- Surface preview computation errors and ActivityNotFoundException to user
- Return boolean from writeExifToUri, log at ERROR level
- Wrap gallery preview downscale in try-catch (OOM protection)

Config:
- Add ACCESS_MEDIA_LOCATION permission (GPS EXIF on Android 10+)
- Accept coarse-only location grant for geotags
- Remove dead adjustResize (no effect with edge-to-edge)
- Set windowBackground to black (eliminates white flash on cold start)
- Add values-night theme for dark mode
- Remove overly broad ProGuard keeps (CameraX/GMS ship consumer rules)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:44:12 +01:00
12051b2a83 Add real-time tilt-shift preview for gallery images
Gallery imports now show the effect live as you adjust parameters,
matching the camera preview experience. Uses a downscaled (1024px)
source bitmap for fast recomputation via collectLatest, which
cancels stale frames when params change mid-computation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:51:26 +01:00
dedf445cf6 Skip saving original when processing gallery images
Gallery imports already have the original on-device, so only save
the tilt-shift processed version. Camera captures continue saving
both versions since the original only exists in memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:40:06 +01:00
983bca3600 Type-safe capture pipeline and image dimension bounds
- Replace unsafe as Any / as SaveResult casts with a sealed
  CaptureOutcome class for type-safe continuation handling
- Catch SecurityException separately with permission-specific messages
- Replace raw e.message with generic user-friendly error strings
- Add inJustDecodeBounds pre-check in loadBitmapFromUri to downsample
  images exceeding 4096px, preventing OOM from huge gallery images

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:56:20 +01:00
5d80dcfcbe Add interactive gallery preview before applying tilt-shift effect
Instead of immediately processing gallery images, show a preview where
users can adjust blur parameters before committing. Adds Cancel/Apply
buttons and hides camera-only controls during gallery preview mode.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:46:05 +01:00
780a8ab167 Add bottom bar gesture exclusion, dual image save, thumbnail preview, and gallery picker
- Increase bottom padding and add systemGestureExclusion to prevent
  accidental navigation gestures from the capture controls
- Save both original and processed images with shared timestamps
  (TILTSHIFT_* and ORIGINAL_*) via new saveBitmapPair() pipeline
- Show animated thumbnail of last captured photo at bottom-right;
  tap opens the image in the default photo viewer
- Add gallery picker button to process existing photos through the
  tilt-shift pipeline with full EXIF rotation support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:32:11 +01:00
f0249fcd64 Add bitmap safety in onCaptureSuccess callback
Track the current bitmap through the decode→rotate→effect pipeline
with a nullable variable. On exception, the in-flight bitmap is
recycled in the catch block to prevent native memory leaks. Errors
are now logged with Log.e and a proper companion TAG.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:20:57 +01:00
593f2c5b1f Add bitmap safety in applyTiltShiftEffect()
Track all intermediate bitmaps with nullable variables and recycle
them in a finally block. This prevents native memory leaks when an
OOM or other exception occurs mid-processing. Variables are set to
null after recycle or handoff to the caller.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:20:16 +01:00
f53d6f0b1b Fix runBlocking deadlock in ImageCaptureHandler
runBlocking on the camera callback thread could deadlock when
saveBitmap() needed the main thread. Split capturePhoto() into two
phases: synchronous CPU work (decode/rotate/effect) inside the
suspendCancellableCoroutine callback, and suspend-safe saveBitmap()
after the continuation resumes in coroutine context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:19:41 +01:00
d3ca23b71c Add radial mode, UI controls, front camera, update to API 35
- Add radial/elliptical blur mode with aspect ratio control
- Add UI sliders for blur intensity, falloff, and shape
- Add front camera support with flip button
- Update minimum SDK to API 35 (Android 15)
- Enable landscape orientation (fullSensor)
- Rename app to "Naiv Tilt Shift Camera"
- Set APK output name to naiv-tilt-shift
- Add project specification document

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 11:13:31 +01:00
e53155e8ee Add 2D focus positioning, fix rotation tracking, sync preview with effect
2D positioning:
- Add positionX parameter to BlurParameters (was only Y before)
- Update shader with uPositionX and uPositionY uniforms
- Single-finger drag now moves focus center anywhere on screen
- Update gradient mask generation for capture

Rotation tracking:
- Remove dampening from rotation gesture (1:1 tracking)
- Rotate gesture now directly tracks finger movement
- Preview effect rotates in sync with overlay

Overlay and shader sync:
- Both now use same positionX, positionY, angle parameters
- Preview blur effect matches overlay visualization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:55:17 +01:00
e3e05af0b8 Fix image orientation, reduce sensitivity, fix overlay clipping
Image orientation:
- Actually rotate captured bitmap using ImageProxy.rotationDegrees
- Save with EXIF ORIENTATION_NORMAL (bitmap already correctly oriented)
- Handle front camera mirroring

Gesture sensitivity (halved again):
- Position drag: 0.15x (was 0.3x)
- Rotation: 0.2x (was 0.4x)
- Size pinch: 0.25x (was 0.5x)
- Zoom pinch: 0.4x (was 0.6x)

Overlay drawing:
- Use screen diagonal to calculate extended geometry
- Draw lines and rectangles that extend beyond screen bounds
- Prevents clipping when tilt-shift effect is rotated

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:46:43 +01:00
07e10ac9c3 Initial implementation of Tilt-Shift Camera Android app
A dedicated camera app for tilt-shift photography with:
- Real-time OpenGL ES 2.0 shader-based blur preview
- Touch gesture controls (drag, rotate, pinch) for adjusting effect
- CameraX integration for camera preview and high-res capture
- EXIF metadata with GPS location support
- MediaStore integration for saving to gallery
- Jetpack Compose UI with haptic feedback

Tech stack: Kotlin, CameraX, OpenGL ES 2.0, Jetpack Compose
Min SDK: 26 (Android 8.0), Target SDK: 35 (Android 15)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 15:26:41 +01:00