Lock activity to portrait; drop all camera-image rotation tracking
Stop trying to rotate the camera image based on device orientation. The activity is now locked to portrait (screenOrientation="portrait"), so the GL surface stays portrait-sized regardless of how the device is held, and the camera passthrough goes back to the simple texCoordsBack 90° rotation that was working before any of the v1.1.6–1.1.13 attempts at landscape support. Net effect: the camera image stays in the device's portrait frame and visually follows the phone as it tilts (since there is no inverse rotation cancelling it). The UI is also locked to the portrait layout for now — a follow-up will add Modifier.graphicsLayer rotations to the icon overlays so they stay readable when the phone is held sideways. screenOrientation switched from fullSensor to portrait; the rest of the file changes are reverts of the orientation plumbing introduced in v1.1.6 and its follow-ups. Bump to 1.1.14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5b553c7196
commit
e4892c4b12
4 changed files with 24 additions and 78 deletions
|
|
@ -31,7 +31,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:screenOrientation="fullSensor"
|
android:screenOrientation="portrait"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||||
android:windowSoftInputMode="adjustNothing"
|
android:windowSoftInputMode="adjustNothing"
|
||||||
android:theme="@style/Theme.TiltShiftCamera">
|
android:theme="@style/Theme.TiltShiftCamera">
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import android.opengl.GLES11Ext
|
||||||
import android.opengl.GLES20
|
import android.opengl.GLES20
|
||||||
import android.opengl.GLSurfaceView
|
import android.opengl.GLSurfaceView
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Surface
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
import java.nio.FloatBuffer
|
import java.nio.FloatBuffer
|
||||||
|
|
@ -70,32 +69,26 @@ class TiltShiftRenderer(
|
||||||
@Volatile
|
@Volatile
|
||||||
private var vertexBufferDirty: Boolean = false
|
private var vertexBufferDirty: Boolean = false
|
||||||
|
|
||||||
// Texture coordinates for the back camera, indexed by Surface.ROTATION_*.
|
// Texture coordinates rotated 90° for portrait mode (back camera)
|
||||||
// The base orientation (index 0) applies the 90° CCW rotation that maps
|
// (Camera sensors are landscape-oriented, we rotate to portrait)
|
||||||
// the landscape sensor frame to a portrait display. Indices 1/2/3 layer
|
private val texCoordsBack = floatArrayOf(
|
||||||
// additional CCW rotations on top so the activity's rotation is
|
1f, 1f, // Bottom left of screen -> bottom right of texture
|
||||||
// compensated and world-up stays at clip-space-top.
|
1f, 0f, // Bottom right of screen -> top right of texture
|
||||||
private val texCoordsBackByRotation = arrayOf(
|
0f, 1f, // Top left of screen -> bottom left of texture
|
||||||
floatArrayOf(1f, 1f, 1f, 0f, 0f, 1f, 0f, 0f), // ROTATION_0
|
0f, 0f // Top right of screen -> top left of texture
|
||||||
floatArrayOf(1f, 0f, 0f, 0f, 1f, 1f, 0f, 1f), // ROTATION_90
|
|
||||||
floatArrayOf(0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f), // ROTATION_180
|
|
||||||
floatArrayOf(0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f) // ROTATION_270
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Front camera variants: same as back, but horizontally mirrored
|
// Texture coordinates for front camera (mirrored + rotated)
|
||||||
// for the natural selfie view.
|
// Front camera needs horizontal mirror for natural selfie view
|
||||||
private val texCoordsFrontByRotation = arrayOf(
|
private val texCoordsFront = floatArrayOf(
|
||||||
floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f), // ROTATION_0
|
0f, 1f, // Bottom left of screen
|
||||||
floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f), // ROTATION_90
|
0f, 0f, // Bottom right of screen
|
||||||
floatArrayOf(1f, 0f, 1f, 1f, 0f, 0f, 0f, 1f), // ROTATION_180
|
1f, 1f, // Top left of screen
|
||||||
floatArrayOf(1f, 1f, 0f, 1f, 1f, 0f, 0f, 0f) // ROTATION_270
|
1f, 0f // Top right of screen
|
||||||
)
|
)
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var displayRotation: Int = Surface.ROTATION_0
|
private var currentTexCoords = texCoordsBack
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var currentTexCoords = texCoordsBackByRotation[Surface.ROTATION_0]
|
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var updateTexCoordBuffer = false
|
private var updateTexCoordBuffer = false
|
||||||
|
|
@ -212,7 +205,8 @@ class TiltShiftRenderer(
|
||||||
fun setFrontCamera(front: Boolean) {
|
fun setFrontCamera(front: Boolean) {
|
||||||
if (isFrontCamera != front) {
|
if (isFrontCamera != front) {
|
||||||
isFrontCamera = front
|
isFrontCamera = front
|
||||||
refreshTexCoords()
|
currentTexCoords = if (front) texCoordsFront else texCoordsBack
|
||||||
|
updateTexCoordBuffer = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -224,27 +218,6 @@ class TiltShiftRenderer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the active display rotation. The texture-coordinate buffer is
|
|
||||||
* rebuilt so the camera image stays world-aligned as the activity rotates
|
|
||||||
* with the device under screenOrientation="fullSensor", and the
|
|
||||||
* crop-to-fill math picks the correct effective aspect ratio.
|
|
||||||
*/
|
|
||||||
fun setDisplayRotation(rotation: Int) {
|
|
||||||
if (displayRotation != rotation) {
|
|
||||||
displayRotation = rotation
|
|
||||||
refreshTexCoords()
|
|
||||||
vertexBufferDirty = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshTexCoords() {
|
|
||||||
val table = if (isFrontCamera) texCoordsFrontByRotation else texCoordsBackByRotation
|
|
||||||
val idx = displayRotation.coerceIn(0, table.size - 1)
|
|
||||||
currentTexCoords = table[idx]
|
|
||||||
updateTexCoordBuffer = true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
shader.release()
|
shader.release()
|
||||||
surfaceTexture?.release()
|
surfaceTexture?.release()
|
||||||
|
|
@ -281,22 +254,16 @@ class TiltShiftRenderer(
|
||||||
/**
|
/**
|
||||||
* Recomputes camera vertex positions to achieve crop-to-fill.
|
* Recomputes camera vertex positions to achieve crop-to-fill.
|
||||||
*
|
*
|
||||||
* The camera sensor is landscape; after the orientation-dependent texcoord
|
* The camera sensor is landscape; after the 90° rotation applied via texture coordinates,
|
||||||
* rotation, the effective dimensions seen on screen are either swapped
|
* the effective portrait dimensions are (cameraHeight × cameraWidth). We scale the vertex
|
||||||
* (portrait orientations) or kept (landscape orientations). We scale the
|
* quad so the camera frame fills the surface without stretching — the GPU clips the overflow.
|
||||||
* vertex quad so the camera frame fills the surface without stretching —
|
|
||||||
* the GPU clips the overflow.
|
|
||||||
*/
|
*/
|
||||||
private fun recomputeVertices() {
|
private fun recomputeVertices() {
|
||||||
var scaleX = 1f
|
var scaleX = 1f
|
||||||
var scaleY = 1f
|
var scaleY = 1f
|
||||||
|
|
||||||
if (cameraWidth > 0 && cameraHeight > 0 && surfaceWidth > 0 && surfaceHeight > 0) {
|
if (cameraWidth > 0 && cameraHeight > 0 && surfaceWidth > 0 && surfaceHeight > 0) {
|
||||||
val isPortrait = displayRotation == Surface.ROTATION_0 ||
|
val cameraRatio = cameraHeight.toFloat() / cameraWidth
|
||||||
displayRotation == Surface.ROTATION_180
|
|
||||||
val effectiveW = if (isPortrait) cameraHeight else cameraWidth
|
|
||||||
val effectiveH = if (isPortrait) cameraWidth else cameraHeight
|
|
||||||
val cameraRatio = effectiveW.toFloat() / effectiveH
|
|
||||||
val screenRatio = surfaceWidth.toFloat() / surfaceHeight
|
val screenRatio = surfaceWidth.toFloat() / surfaceHeight
|
||||||
|
|
||||||
if (cameraRatio > screenRatio) {
|
if (cameraRatio > screenRatio) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
package no.naiv.tiltshift.ui
|
package no.naiv.tiltshift.ui
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.SurfaceTexture
|
import android.graphics.SurfaceTexture
|
||||||
import android.hardware.display.DisplayManager
|
|
||||||
import android.opengl.GLSurfaceView
|
import android.opengl.GLSurfaceView
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Display
|
|
||||||
import android.view.Surface
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
|
|
@ -168,23 +164,6 @@ fun CameraScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start camera when surface texture is available
|
// Start camera when surface texture is available
|
||||||
LaunchedEffect(surfaceTexture) {
|
LaunchedEffect(surfaceTexture) {
|
||||||
surfaceTexture?.let {
|
surfaceTexture?.let {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
versionMajor=1
|
versionMajor=1
|
||||||
versionMinor=1
|
versionMinor=1
|
||||||
versionPatch=13
|
versionPatch=14
|
||||||
versionCode=15
|
versionCode=16
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue