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>
This commit is contained in:
Ole-Morten Duesund 2026-05-11 16:22:54 +02:00
commit 5b553c7196
2 changed files with 20 additions and 7 deletions

View file

@ -1,9 +1,13 @@
package no.naiv.tiltshift.ui
import android.content.Context
import android.content.Intent
import android.graphics.SurfaceTexture
import android.hardware.display.DisplayManager
import android.opengl.GLSurfaceView
import android.util.Log
import android.view.Display
import android.view.Surface
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -164,11 +168,20 @@ fun CameraScreen(
}
}
// Forward device rotation to renderer so the camera image stays
// world-aligned as the activity rotates with the device.
val currentRotation by viewModel.currentRotation.collectAsState()
LaunchedEffect(currentRotation, renderer) {
renderer?.setDisplayRotation(currentRotation)
// Forward the activity's actual rotation (Display.rotation) to the
// renderer so the camera image stays world-aligned as the activity rotates
// with the device. Don't drive this from OrientationEventListener — its
// 45° threshold fires *before* the activity has rotated, briefly leaving
// the texcoord set out of sync with the GL surface orientation.
// LocalConfiguration triggers a recomposition on configuration change,
// which is when Display.rotation can have changed.
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
val displayRotation = remember(configuration) {
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
displayManager.getDisplay(Display.DEFAULT_DISPLAY)?.rotation ?: Surface.ROTATION_0
}
LaunchedEffect(displayRotation, renderer) {
renderer?.setDisplayRotation(displayRotation)
glSurfaceView?.requestRender()
}

View file

@ -1,4 +1,4 @@
versionMajor=1
versionMinor=1
versionPatch=12
versionCode=14
versionPatch=13
versionCode=15