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>
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>
Migrate all UI state from local remember{} to CameraViewModel for
surviving configuration changes. Add processing overlay indicator,
accessibility semantics on interactive elements, gallery preview
with Cancel/Apply flow, and consistent bottom bar layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Null the Compose state reference before recycling bitmaps to prevent
the renderer from drawing a recycled bitmap between recycle() and
the state update
- Wrap ACTION_VIEW startActivity in try-catch for devices without
an image viewer installed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
The feature provided no benefit on Pixel 7 Pro — both standard and
hi-res modes produced 12MP images since CameraX's standard resolution
list doesn't include the full sensor output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add an error StateFlow to CameraManager so camera binding failures
are surfaced to the user instead of silently swallowed by
e.printStackTrace(). CameraScreen collects this flow and displays
errors using the existing red overlay UI. Added Log.e with proper
TAG for logcat visibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add separate texture coordinates for front and back cameras
- Front camera uses mirrored coordinates for natural selfie view
- Add setFrontCamera() method to renderer for dynamic switching
- Update texture coord buffer on GL thread when camera changes
- CameraScreen observes isFrontCamera state to trigger updates
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use rememberUpdatedState in ControlPanel to prevent stale closure
capture during continuous slider drags. This ensures the latest
blur parameters are used when updating, avoiding conflicts with
concurrent gesture updates from TiltShiftOverlay.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 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>
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>