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.GLSurfaceView
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
|
|
@ -69,26 +70,32 @@ class TiltShiftRenderer(
|
|||
@Volatile
|
||||
private var vertexBufferDirty: Boolean = false
|
||||
|
||||
// Texture coordinates rotated 90° for portrait mode (back camera)
|
||||
// (Camera sensors are landscape-oriented, we rotate to portrait)
|
||||
private val texCoordsBack = floatArrayOf(
|
||||
1f, 1f, // Bottom left of screen -> bottom right of texture
|
||||
1f, 0f, // Bottom right of screen -> top right of texture
|
||||
0f, 1f, // Top left of screen -> bottom left of texture
|
||||
0f, 0f // Top right of screen -> top left of texture
|
||||
// Texture coordinates for the back camera, indexed by Surface.ROTATION_*.
|
||||
// The base orientation (index 0) applies the 90° CCW rotation that maps
|
||||
// the landscape sensor frame to a portrait display. Indices 1/2/3 layer
|
||||
// additional CCW rotations on top so the activity's rotation is
|
||||
// compensated and world-up stays at clip-space-top.
|
||||
private val texCoordsBackByRotation = arrayOf(
|
||||
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 needs horizontal mirror for natural selfie view
|
||||
private val texCoordsFront = floatArrayOf(
|
||||
0f, 1f, // Bottom left of screen
|
||||
0f, 0f, // Bottom right of screen
|
||||
1f, 1f, // Top left of screen
|
||||
1f, 0f // Top right of screen
|
||||
// Front camera variants: same as back, but horizontally mirrored
|
||||
// for the natural selfie view.
|
||||
private val texCoordsFrontByRotation = arrayOf(
|
||||
floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f), // ROTATION_0
|
||||
floatArrayOf(0f, 0f, 1f, 0f, 0f, 1f, 1f, 1f), // ROTATION_90
|
||||
floatArrayOf(1f, 0f, 1f, 1f, 0f, 0f, 0f, 1f), // ROTATION_180
|
||||
floatArrayOf(1f, 1f, 0f, 1f, 1f, 0f, 0f, 0f) // ROTATION_270
|
||||
)
|
||||
|
||||
@Volatile
|
||||
private var currentTexCoords = texCoordsBack
|
||||
private var displayRotation: Int = Surface.ROTATION_0
|
||||
|
||||
@Volatile
|
||||
private var currentTexCoords = texCoordsBackByRotation[Surface.ROTATION_0]
|
||||
|
||||
@Volatile
|
||||
private var updateTexCoordBuffer = false
|
||||
|
|
@ -205,8 +212,7 @@ class TiltShiftRenderer(
|
|||
fun setFrontCamera(front: Boolean) {
|
||||
if (isFrontCamera != front) {
|
||||
isFrontCamera = front
|
||||
currentTexCoords = if (front) texCoordsFront else texCoordsBack
|
||||
updateTexCoordBuffer = true
|
||||
refreshTexCoords()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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() {
|
||||
shader.release()
|
||||
surfaceTexture?.release()
|
||||
|
|
@ -254,16 +281,22 @@ class TiltShiftRenderer(
|
|||
/**
|
||||
* Recomputes camera vertex positions to achieve crop-to-fill.
|
||||
*
|
||||
* The camera sensor is landscape; after the 90° rotation applied via texture coordinates,
|
||||
* the effective portrait dimensions are (cameraHeight × cameraWidth). We scale the vertex
|
||||
* quad so the camera frame fills the surface without stretching — the GPU clips the overflow.
|
||||
* The camera sensor is landscape; after the orientation-dependent texcoord
|
||||
* rotation, the effective dimensions seen on screen are either swapped
|
||||
* (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() {
|
||||
var scaleX = 1f
|
||||
var scaleY = 1f
|
||||
|
||||
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
|
||||
|
||||
if (cameraRatio > screenRatio) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue