- 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>
- Catch SecurityException separately for storage permission revocation
- Replace raw e.message with generic user-friendly error strings
- Replace thread-unsafe SimpleDateFormat with java.time.DateTimeFormatter
to prevent filename collisions under concurrent saves on Dispatchers.IO
- Remove deprecated MediaStore.Images.Media.DATA column query and the
path field from SaveResult.Success (unreliable on scoped storage)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Catch SecurityException separately with permission-specific message
- Replace raw e.message with generic user-friendly error strings
- Wrap cameraProviderFuture.get() in try-catch to handle CameraX
initialization failures instead of crashing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A revoked location permission was previously caught and sent as null
with zero logging, making it indistinguishable from "no fix yet" and
impossible to diagnose.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use safe cast (as? VibratorManager) and wrap vibrate calls in
try-catch to prevent crashes on emulators, custom ROMs, or
devices without vibration hardware.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Uncomment *.jks and *.keystore in .gitignore to prevent
accidental keystore commits
- Disable android:allowBackup to prevent ADB data extraction
- Add distributionSha256Sum to gradle-wrapper.properties for
tamper detection of Gradle distributions
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 useHighResCapture toggle to CameraManager that switches between
CameraX default resolution and HIGHEST_AVAILABLE_STRATEGY. Default
is off to avoid OOM from processing very large bitmaps (e.g. 50MP).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
With minSdk=35 all Build.VERSION.SDK_INT checks for API levels below
35 are always true. Remove all version branching in HapticFeedback
(API 29/31 checks) and PhotoSaver (API 29 checks). Keep only the
modern API calls and drop @Suppress("DEPRECATION") annotations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Delete ExifWriter.kt (instantiated but never called)
- Remove saveJpegFile() and unused imports from PhotoSaver
- Remove CameraFlipButton() and unused imports from LensSwitcher
- Remove companion object and unused imports from HapticFeedback
- Remove getZoomPresets() from LensController
- Update README to reflect ExifWriter removal and actual minSdk (35)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EXIF write failures are non-critical (the photo is already saved)
but should still be visible in logcat. Use Log.w with a proper TAG
instead of printStackTrace().
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>
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>
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>
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>
- Load signing credentials from local.properties (not committed)
- Keystore stored in .signing/ directory (not committed)
- Release builds are now signed and installable
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add uIsFrontCamera uniform to shader and adjust coordinate
transformations for front camera's mirrored texture coordinates:
- Position transform: (1-posY, 1-posX) instead of (posY, 1-posX)
- Angle transform: -angle - 90° instead of +angle + 90°
Applied to linearFocusDistance, radialFocusDistance, and blur
direction calculations in sampleBlurred.
Co-Authored-By: Claude Opus 4.5 <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>
Apply screen aspect ratio correction to offset.y (screen X direction)
instead of offset.x (screen Y direction) in both linear and radial
mode distance calculations. This fixes angle distortion at non-90°
rotations in portrait mode.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Transform screen coordinates to texture coordinates (90° CW rotation):
- Position: (x,y) -> (y, 1-x)
- Angle: θ -> θ + 90°
Applied in linearFocusDistance, radialFocusDistance, and sampleBlurred
to fix preview not matching UI overlay position and rotation.
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>
State persistence:
- Use rememberUpdatedState to always get latest params inside pointerInput
- Capture gestureStartParams at beginning of each gesture
- All adjustments now use initial values + accumulated change
Drag tracking:
- Track initialDragCentroid at drag start
- Calculate total drag offset from initial point (not frame-by-frame)
- Drag now properly moves focus center 1:1
Shader rotation sync:
- Adjust angle by -90° in shader to compensate for portrait texture rotation
- Preview blur effect now rotates in sync with overlay UI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Rotation:
- Use direct angle calculation between touch points (atan2)
- Track initial touch angle and effect angle separately
- Effect rotation now matches finger rotation exactly
Position drag:
- Remove all sensitivity dampening
- 1:1 mapping: finger moves 100px, effect center moves 100px
Gesture zones rebalanced:
- Rotation: only very center of focus zone (< 30% of focus height)
- Size adjustment: large area around effect (30% - 200%)
- Camera zoom: only far outside the effect (> 200%)
This prevents rotation from dominating size adjustments.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
- Move #extension directive to first line (required by GLSL)
- Replace array initializer syntax with getWeight() function
(float[]() constructor not supported in GLSL ES 1.00)
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>