Commit graph

17 commits

Author SHA1 Message Date
5b553c7196 Drive renderer rotation from Display.rotation, not OrientationEventListener
OrientationEventListener fires continuously on the raw accelerometer
tilt and crosses the ROTATION_90 / ROTATION_270 boundary at 45° — well
before the system actually rotates the activity. The renderer was
swapping its texcoord buffer at 45° tilt while the GL surface and
Compose layout were still in the previous orientation, so for the few
degrees between "OrientationEventListener fires" and "activity
rotates" the camera image rendered at the wrong rotation. Past that
window it snapped back into sync.

Use LocalConfiguration + Display.rotation to source the renderer's
rotation. Configuration only changes when the activity has actually
rotated, so the texcoord buffer flips in lock-step with the GL surface
and there is no transient mis-orientation. OrientationEventListener
is still used by capture for EXIF metadata.

Bump to 1.1.13.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:22:54 +02:00
a2dfa7db3d Make camera image follow device rotation (4-orientation texcoord table)
Re-add landscape support, this time via four precomputed texcoord
buffers — one per Surface.ROTATION_* — instead of going through
SurfaceTexture.getTransformMatrix() (which doesn't honour
Preview.targetRotation for custom SurfaceProviders) or the manual
matrix composition attempts in v1.1.6–1.1.11.

For each device orientation the renderer picks the texcoord set that
both compensates for the 90° CW sensor mount and the activity's own
rotation under screenOrientation="fullSensor", so world-up stays at
clip-space-top. recomputeVertices swaps effective camera dimensions
between portrait and landscape so crop-to-fill picks the right aspect.

Verified empirically in the emulator across all four Display.rotation
values (sky-yellow band always lands at the top of the screen).

Bump to 1.1.12.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 16:12:29 +02:00
b0691adfa3 Reapply "Revert orientation tracking on the camera image"
This reverts commit c0bab85d63.
2026-05-11 15:57:32 +02:00
dd4471c7d2 Revert "Flip rotation-correction angles for both landscape orientations"
This reverts commit 1cd2b0a57c.
2026-05-11 15:57:32 +02:00
1cd2b0a57c Flip rotation-correction angles for both landscape orientations
Bring back the v1.1.9 approach (apply an inverse rotation to the
texcoord sampling pattern so the camera image stays world-aligned
through device rotation), but with the right signs this time. The
previous angles were 180° off for both landscape orientations and
showed the camera content upside-down on a real device.

Verified each Display.rotation against the emulator's virtual scene
(sky-yellow → road-brown → buildings-dark): the sky/yellow band now
sits at the top of the screen in all four orientations.

Bump to 1.1.11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 15:46:31 +02:00
c0bab85d63 Revert "Revert orientation tracking on the camera image"
This reverts commit 4f8661f648.
2026-05-11 15:29:59 +02:00
4f8661f648 Revert orientation tracking on the camera image
Roll back the rendering-pipeline changes from v1.1.6 (and the
subsequent attempts in 1.1.7/1.1.8/1.1.9): no more
SurfaceTexture transform-matrix-driven re-orientation, no more
rebinding on rotation, no more inverse-rotation correction. The
camera passthrough goes back to fixed portrait-oriented texcoords
and the crop-to-fill math treats the camera buffer as portrait
unconditionally.

The activity stays `fullSensor`, so the Compose UI and the GL
blur passes continue to follow the device orientation — only the
camera image itself is left untouched, which matches the
"normal camera doesn't flip the picture when rotating the phone"
behaviour requested.

Bump to 1.1.10.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:59:18 +02:00
6d7be66341 Fix one landscape orientation rendering image upside down
`SurfaceTexture.getTransformMatrix()` for a custom SurfaceProvider does
not vary with `Preview.targetRotation` — it only encodes the static
sensor-to-buffer transform. The v1.1.8 rebind-on-rotation fix did
update Preview's target rotation, but the matrix returned to the GL
renderer was identical across all four device orientations. Combined
with the activity rotating under fullSensor (so the GL clip-space "up"
direction tracks the device, not the world), one of the two landscape
orientations rendered the image upside down on a real device. The
emulator masked this because its virtual scene is roughly symmetric.

Compose the missing piece on the GL thread: rotate the texcoord
sampling pattern around its centre by the inverse of the activity
rotation before sampling the camera texture. The four orientations
now produce four distinct matrices, keeping world-up at screen-up in
all of them while leaving portrait unchanged.

Bump to 1.1.9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:46:28 +02:00
f0d81db068 Fix landscape preview by rebinding on rotation change
`Preview.targetRotation = …` on an already-bound use case does not
refresh the live SurfaceTexture's transform matrix or trigger a new
SurfaceRequest with a rotation-appropriate buffer size — the new
rotation only applies to subsequently bound streams. With our custom
SurfaceProvider, the result was that rotating to landscape left the
camera still writing portrait-oriented frames into the now-landscape
GL surface, so the preview appeared to rotate with the device instead
of switching to landscape.

Rebind the camera use cases when the target rotation changes so
CameraX fires a fresh SurfaceRequest with the correct resolution
and matrix. Also revert the OrientationDetector mapping back to its
original (matches Display.rotation for a fullSensor activity, which
is what setTargetRotation expects); the previous "fix" went the wrong
direction and was not the root cause.

Bump to 1.1.8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 14:05:45 +02:00
5eb476a059 Fix landscape image rotating opposite to device
OrientationDetector mapped the OrientationEventListener angle to
Surface.ROTATION_* with 90 and 270 swapped relative to the CameraX
docs example. The angle reports the device's physical clockwise
rotation, whereas Surface.ROTATION_* describes the screen's logical
rotation (opposite direction), so the values must be inverted.

Symptom: rotating the phone clockwise rotated the live preview counter-
clockwise (and vice versa), instead of keeping world-up at screen-up.
The bug was previously masked because the only consumer of the value
(deviceRotation in capturePhoto) is unused — the live preview commit
(d321f07) wired this same value into CameraX's setTargetRotation, which
exposed the inverted mapping.

Bump to 1.1.7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:51:17 +02:00
d321f07973 Support landscape orientation
Replace hardcoded portrait-only texture coordinate rotation with
SurfaceTexture.getTransformMatrix(), so the camera preview and capture
re-orient correctly when the device rotates. Also drive
Preview/ImageCapture targetRotation from the live display rotation, fix
the crop-to-fill aspect math to swap effective camera dimensions
between portrait and landscape, and make the slider control panel
scroll if it doesn't fit the shorter landscape height.

Bump to 1.1.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 16:31:43 +02:00
2633f261ef Bump version to 1.1.5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:52:33 +01:00
5b9aedd109 Update all dependencies to March 2026 versions
Major version bumps:
- AGP 8.7.3 → 9.1.0 (remove kotlin-android plugin, now built-in)
- Kotlin 2.0.21 → 2.3.20
- Gradle 8.9 → 9.4.0
- Compose BOM 2024.12.01 → 2026.03.00
- compileSdk/targetSdk 35 → 36

Library updates:
- core-ktx 1.15.0 → 1.18.0
- lifecycle 2.8.7 → 2.10.0
- activity-compose 1.9.3 → 1.13.0
- CameraX 1.4.1 → 1.5.1
- exifinterface 1.3.7 → 1.4.2

AGP 9 migration: removed org.jetbrains.kotlin.android plugin (Kotlin
support is now built into AGP), removed kotlinOptions block (JVM
target handled by compileOptions).

Bump version to 1.1.4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:46:37 +01:00
f3baa723be Implement two-pass separable Gaussian blur for camera preview
Replace the single-pass 9-tap directional blur with a three-pass
pipeline: passthrough (camera→FBO), horizontal blur (FBO-A→FBO-B),
vertical blur (FBO-B→screen). This produces a true 2D Gaussian with
a 13-tap kernel per pass, eliminating the visible banding/streaking
of the old approach.

Key changes:
- TiltShiftRenderer: FBO ping-pong with two color textures, separate
  fullscreen quad for blur passes (no crop-to-fill), drawQuad helper
- TiltShiftShader: manages two programs (passthrough + blur), blur
  program uses raw screen-space angle (no camera rotation adjustment)
- tiltshift_fragment.glsl: rewritten for sampler2D in screen space,
  aspect correction on X axis (height-normalized), uBlurDirection
  uniform for H/V selection, wider falloff (3x multiplier)
- New tiltshift_passthrough_fragment.glsl for camera→FBO copy
- TiltShiftOverlay: shrink PINCH_SIZE zone (1.3x, was 2.0x) so
  pinch-to-zoom is reachable over more of the screen
- CameraManager: optimistic zoom update fixes pinch-to-zoom stalling
  (stale zoomRatio base prevented delta accumulation)

Bump version to 1.1.3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:38:50 +01:00
878c23bf89 Replace Accompanist Permissions with first-party activity-compose API
Accompanist Permissions (0.36.0) is deprecated and experimental. Migrate
to the stable ActivityResultContracts.RequestPermission /
RequestMultiplePermissions APIs already available via activity-compose.

Adds explicit state tracking with a cameraResultReceived flag to
correctly distinguish "never asked" from "permanently denied" — an
improvement over the previous Accompanist-based detection.

Bump version to 1.1.2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:43:56 +01:00
1212604cf7 Bump version to 1.1.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:58:40 +01:00
52af9f6047 Add version management with auto-bump script
Version is tracked in version.properties and read by build.gradle.kts.
Run ./bump-version.sh [major|minor|patch] before release builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:58:22 +01:00