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>
This commit is contained in:
parent
b0691adfa3
commit
a2dfa7db3d
3 changed files with 64 additions and 23 deletions
|
|
@ -6,6 +6,7 @@ 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
|
||||||
|
|
@ -69,26 +70,32 @@ class TiltShiftRenderer(
|
||||||
@Volatile
|
@Volatile
|
||||||
private var vertexBufferDirty: Boolean = false
|
private var vertexBufferDirty: Boolean = false
|
||||||
|
|
||||||
// Texture coordinates rotated 90° for portrait mode (back camera)
|
// Texture coordinates for the back camera, indexed by Surface.ROTATION_*.
|
||||||
// (Camera sensors are landscape-oriented, we rotate to portrait)
|
// The base orientation (index 0) applies the 90° CCW rotation that maps
|
||||||
private val texCoordsBack = floatArrayOf(
|
// the landscape sensor frame to a portrait display. Indices 1/2/3 layer
|
||||||
1f, 1f, // Bottom left of screen -> bottom right of texture
|
// additional CCW rotations on top so the activity's rotation is
|
||||||
1f, 0f, // Bottom right of screen -> top right of texture
|
// compensated and world-up stays at clip-space-top.
|
||||||
0f, 1f, // Top left of screen -> bottom left of texture
|
private val texCoordsBackByRotation = arrayOf(
|
||||||
0f, 0f // Top right of screen -> top left of texture
|
floatArrayOf(1f, 1f, 1f, 0f, 0f, 1f, 0f, 0f), // ROTATION_0
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
// Texture coordinates for front camera (mirrored + rotated)
|
// Front camera variants: same as back, but horizontally mirrored
|
||||||
// Front camera needs horizontal mirror for natural selfie view
|
// for the natural selfie view.
|
||||||
private val texCoordsFront = floatArrayOf(
|
private val texCoordsFrontByRotation = arrayOf(
|
||||||
0f, 1f, // Bottom left of screen
|
floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f), // ROTATION_0
|
||||||
0f, 0f, // Bottom right of screen
|
floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f), // ROTATION_90
|
||||||
1f, 1f, // Top left of screen
|
floatArrayOf(1f, 0f, 1f, 1f, 0f, 0f, 0f, 1f), // ROTATION_180
|
||||||
1f, 0f // Top right of screen
|
floatArrayOf(1f, 1f, 0f, 1f, 1f, 0f, 0f, 0f) // ROTATION_270
|
||||||
)
|
)
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var currentTexCoords = texCoordsBack
|
private var displayRotation: Int = Surface.ROTATION_0
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var currentTexCoords = texCoordsBackByRotation[Surface.ROTATION_0]
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var updateTexCoordBuffer = false
|
private var updateTexCoordBuffer = false
|
||||||
|
|
@ -205,8 +212,7 @@ class TiltShiftRenderer(
|
||||||
fun setFrontCamera(front: Boolean) {
|
fun setFrontCamera(front: Boolean) {
|
||||||
if (isFrontCamera != front) {
|
if (isFrontCamera != front) {
|
||||||
isFrontCamera = front
|
isFrontCamera = front
|
||||||
currentTexCoords = if (front) texCoordsFront else texCoordsBack
|
refreshTexCoords()
|
||||||
updateTexCoordBuffer = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,6 +224,27 @@ 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()
|
||||||
|
|
@ -254,16 +281,22 @@ 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 90° rotation applied via texture coordinates,
|
* The camera sensor is landscape; after the orientation-dependent texcoord
|
||||||
* the effective portrait dimensions are (cameraHeight × cameraWidth). We scale the vertex
|
* rotation, the effective dimensions seen on screen are either swapped
|
||||||
* quad so the camera frame fills the surface without stretching — the GPU clips the overflow.
|
* (portrait orientations) or kept (landscape orientations). We scale the
|
||||||
|
* 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 cameraRatio = cameraHeight.toFloat() / cameraWidth
|
val isPortrait = displayRotation == Surface.ROTATION_0 ||
|
||||||
|
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) {
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,14 @@ 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)
|
||||||
|
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=10
|
versionPatch=12
|
||||||
versionCode=12
|
versionCode=14
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue