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>
This commit is contained in:
parent
6d7be66341
commit
4f8661f648
7 changed files with 55 additions and 171 deletions
|
|
@ -5,9 +5,7 @@ import android.graphics.SurfaceTexture
|
|||
import android.opengl.GLES11Ext
|
||||
import android.opengl.GLES20
|
||||
import android.opengl.GLSurfaceView
|
||||
import android.opengl.Matrix
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.nio.FloatBuffer
|
||||
|
|
@ -40,8 +38,9 @@ class TiltShiftRenderer(
|
|||
private var surfaceTexture: SurfaceTexture? = null
|
||||
private var cameraTextureId: Int = 0
|
||||
|
||||
// Camera quad: crop-to-fill vertices, standard texcoords (rotation comes from texMatrix)
|
||||
// Camera quad: crop-to-fill vertices + rotated texcoords (pass 1 only)
|
||||
private lateinit var cameraVertexBuffer: FloatBuffer
|
||||
private lateinit var cameraTexCoordBuffer: FloatBuffer
|
||||
|
||||
// Fullscreen quad for blur passes (no crop, standard texcoords)
|
||||
private lateinit var fullscreenVertexBuffer: FloatBuffer
|
||||
|
|
@ -55,11 +54,6 @@ class TiltShiftRenderer(
|
|||
private var fboTexA: Int = 0
|
||||
private var fboTexB: Int = 0
|
||||
|
||||
// SurfaceTexture transform matrix, refreshed each frame on the GL thread.
|
||||
private val texMatrix = FloatArray(16)
|
||||
// Sensor-to-buffer matrix from SurfaceTexture before display-rotation correction.
|
||||
private val sensorMatrix = FloatArray(16)
|
||||
|
||||
// Current effect parameters (updated from UI thread)
|
||||
@Volatile
|
||||
var blurParameters: BlurParameters = BlurParameters.DEFAULT
|
||||
|
|
@ -72,14 +66,33 @@ class TiltShiftRenderer(
|
|||
private var cameraWidth: Int = 0
|
||||
@Volatile
|
||||
private var cameraHeight: Int = 0
|
||||
|
||||
/** Display rotation as a Surface.ROTATION_* constant; affects effective aspect. */
|
||||
@Volatile
|
||||
private var displayRotation: Int = Surface.ROTATION_0
|
||||
|
||||
@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 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
|
||||
)
|
||||
|
||||
@Volatile
|
||||
private var currentTexCoords = texCoordsBack
|
||||
|
||||
@Volatile
|
||||
private var updateTexCoordBuffer = false
|
||||
|
||||
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
|
||||
GLES20.glClearColor(0f, 0f, 0f, 1f)
|
||||
|
||||
|
|
@ -91,8 +104,12 @@ class TiltShiftRenderer(
|
|||
cameraVertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f))
|
||||
cameraVertexBuffer.position(0)
|
||||
|
||||
// Fullscreen quad for blur passes (standard coords). The same buffer is reused
|
||||
// for the camera passthrough texcoords — rotation is applied via uTexMatrix.
|
||||
// Camera texcoord buffer (rotated for portrait)
|
||||
cameraTexCoordBuffer = allocateFloatBuffer(8)
|
||||
cameraTexCoordBuffer.put(currentTexCoords)
|
||||
cameraTexCoordBuffer.position(0)
|
||||
|
||||
// Fullscreen quad for blur passes (standard coords)
|
||||
fullscreenVertexBuffer = allocateFloatBuffer(8)
|
||||
fullscreenVertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f))
|
||||
fullscreenVertexBuffer.position(0)
|
||||
|
|
@ -128,19 +145,20 @@ class TiltShiftRenderer(
|
|||
}
|
||||
|
||||
override fun onDrawFrame(gl: GL10?) {
|
||||
val st = surfaceTexture
|
||||
st?.updateTexImage()
|
||||
// SurfaceTexture's transform matrix only handles the sensor-to-buffer
|
||||
// orientation; for a custom SurfaceProvider it does NOT vary with
|
||||
// Preview.targetRotation. Apply the display-rotation correction here.
|
||||
st?.getTransformMatrix(sensorMatrix)
|
||||
composeTexMatrix()
|
||||
surfaceTexture?.updateTexImage()
|
||||
|
||||
if (vertexBufferDirty) {
|
||||
recomputeVertices()
|
||||
vertexBufferDirty = false
|
||||
}
|
||||
|
||||
if (updateTexCoordBuffer) {
|
||||
cameraTexCoordBuffer.clear()
|
||||
cameraTexCoordBuffer.put(currentTexCoords)
|
||||
cameraTexCoordBuffer.position(0)
|
||||
updateTexCoordBuffer = false
|
||||
}
|
||||
|
||||
val params = blurParameters
|
||||
|
||||
// --- Pass 1: Camera → FBO-A (passthrough with crop-to-fill) ---
|
||||
|
|
@ -151,10 +169,10 @@ class TiltShiftRenderer(
|
|||
)
|
||||
GLES20.glViewport(0, 0, surfaceWidth, surfaceHeight)
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
|
||||
shader.usePassthrough(cameraTextureId, texMatrix, isFrontCamera)
|
||||
shader.usePassthrough(cameraTextureId)
|
||||
drawQuad(
|
||||
shader.passthroughPositionLoc, shader.passthroughTexCoordLoc,
|
||||
cameraVertexBuffer, fullscreenTexCoordBuffer
|
||||
cameraVertexBuffer, cameraTexCoordBuffer
|
||||
)
|
||||
|
||||
// --- Pass 2: FBO-A → FBO-B (horizontal blur) ---
|
||||
|
|
@ -185,7 +203,11 @@ class TiltShiftRenderer(
|
|||
}
|
||||
|
||||
fun setFrontCamera(front: Boolean) {
|
||||
isFrontCamera = front
|
||||
if (isFrontCamera != front) {
|
||||
isFrontCamera = front
|
||||
currentTexCoords = if (front) texCoordsFront else texCoordsBack
|
||||
updateTexCoordBuffer = true
|
||||
}
|
||||
}
|
||||
|
||||
fun setCameraResolution(width: Int, height: Int) {
|
||||
|
|
@ -196,49 +218,6 @@ class TiltShiftRenderer(
|
|||
}
|
||||
}
|
||||
|
||||
/** Updates the display rotation so crop-to-fill picks the right effective aspect. */
|
||||
fun setDisplayRotation(rotation: Int) {
|
||||
if (displayRotation != rotation) {
|
||||
displayRotation = rotation
|
||||
vertexBufferDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the sensor-to-buffer matrix with a rotation that compensates for
|
||||
* the activity's current rotation. The activity rotates with the device
|
||||
* (screenOrientation="fullSensor"), so the GL clip-space "up" direction
|
||||
* tracks the device rather than the world. To keep world-up at screen-up
|
||||
* regardless of orientation, rotate the texcoord sampling pattern by the
|
||||
* inverse of the activity rotation. Without this correction, the two
|
||||
* landscape orientations would render the same matrix and one would appear
|
||||
* upside-down on a real device.
|
||||
*/
|
||||
private fun composeTexMatrix() {
|
||||
// Inverse of the activity rotation: rotating the sampling pattern by
|
||||
// -activityAngle puts the world-aligned point originally at screen P
|
||||
// at the same screen P after the activity has rotated.
|
||||
val angle = when (displayRotation) {
|
||||
Surface.ROTATION_90 -> -90f
|
||||
Surface.ROTATION_180 -> 180f
|
||||
Surface.ROTATION_270 -> 90f
|
||||
else -> 0f
|
||||
}
|
||||
if (angle == 0f) {
|
||||
System.arraycopy(sensorMatrix, 0, texMatrix, 0, 16)
|
||||
return
|
||||
}
|
||||
// Build rotation around the (0.5, 0.5) texcoord center.
|
||||
Matrix.setIdentityM(texMatrix, 0)
|
||||
Matrix.translateM(texMatrix, 0, 0.5f, 0.5f, 0f)
|
||||
Matrix.rotateM(texMatrix, 0, angle, 0f, 0f, 1f)
|
||||
Matrix.translateM(texMatrix, 0, -0.5f, -0.5f, 0f)
|
||||
// texMatrix = sensorMatrix * rotation (rotation is applied to the
|
||||
// texcoord first, then the sensor-to-buffer transform).
|
||||
val rot = texMatrix.copyOf()
|
||||
Matrix.multiplyMM(texMatrix, 0, sensorMatrix, 0, rot, 0)
|
||||
}
|
||||
|
||||
fun release() {
|
||||
shader.release()
|
||||
surfaceTexture?.release()
|
||||
|
|
@ -275,24 +254,16 @@ class TiltShiftRenderer(
|
|||
/**
|
||||
* Recomputes camera vertex positions to achieve crop-to-fill.
|
||||
*
|
||||
* The camera buffer is the sensor's native landscape resolution. The texMatrix
|
||||
* rotates it to match the display, so the *effective* displayed dimensions
|
||||
* depend on the display rotation: in portrait the buffer is rotated 90°
|
||||
* (effective width = cameraHeight), in landscape it is unrotated.
|
||||
* We scale the vertex quad so the frame fills the surface without stretching —
|
||||
* the GPU clips the overflow.
|
||||
* 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.
|
||||
*/
|
||||
private fun recomputeVertices() {
|
||||
var scaleX = 1f
|
||||
var scaleY = 1f
|
||||
|
||||
if (cameraWidth > 0 && cameraHeight > 0 && surfaceWidth > 0 && surfaceHeight > 0) {
|
||||
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 cameraRatio = cameraHeight.toFloat() / cameraWidth
|
||||
val screenRatio = surfaceWidth.toFloat() / surfaceHeight
|
||||
|
||||
if (cameraRatio > screenRatio) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue