Compare commits
No commits in common. "1212604cf78b4a36902b630c84e876e9d7260504" and "4d755dce315af7744479e3e8f61fa07631789c39" have entirely different histories.
1212604cf7
...
4d755dce31
6 changed files with 20 additions and 174 deletions
|
|
@ -14,15 +14,6 @@ val keystoreProperties = Properties().apply {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load version from version.properties
|
|
||||||
val versionProperties = Properties().apply {
|
|
||||||
load(rootProject.file("version.properties").inputStream())
|
|
||||||
}
|
|
||||||
val vMajor = versionProperties["versionMajor"].toString().toInt()
|
|
||||||
val vMinor = versionProperties["versionMinor"].toString().toInt()
|
|
||||||
val vPatch = versionProperties["versionPatch"].toString().toInt()
|
|
||||||
val vCode = versionProperties["versionCode"].toString().toInt()
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "no.naiv.tiltshift"
|
namespace = "no.naiv.tiltshift"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
@ -43,8 +34,8 @@ android {
|
||||||
applicationId = "no.naiv.tiltshift"
|
applicationId = "no.naiv.tiltshift"
|
||||||
minSdk = 35
|
minSdk = 35
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = vCode
|
versionCode = 1
|
||||||
versionName = "$vMajor.$vMinor.$vPatch"
|
versionName = "1.0.0"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,6 @@ class CameraManager(private val context: Context) {
|
||||||
private val _isFrontCamera = MutableStateFlow(false)
|
private val _isFrontCamera = MutableStateFlow(false)
|
||||||
val isFrontCamera: StateFlow<Boolean> = _isFrontCamera.asStateFlow()
|
val isFrontCamera: StateFlow<Boolean> = _isFrontCamera.asStateFlow()
|
||||||
|
|
||||||
private val _previewResolution = MutableStateFlow(Size(0, 0))
|
|
||||||
val previewResolution: StateFlow<Size> = _previewResolution.asStateFlow()
|
|
||||||
|
|
||||||
/** Background executor for image capture callbacks to avoid blocking the main thread. */
|
/** Background executor for image capture callbacks to avoid blocking the main thread. */
|
||||||
private val captureExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
private val captureExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
|
@ -164,7 +161,6 @@ class CameraManager(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
surfaceSize = request.resolution
|
surfaceSize = request.resolution
|
||||||
_previewResolution.value = surfaceSize
|
|
||||||
surfaceTexture.setDefaultBufferSize(surfaceSize.width, surfaceSize.height)
|
surfaceTexture.setDefaultBufferSize(surfaceSize.width, surfaceSize.height)
|
||||||
|
|
||||||
val surface = Surface(surfaceTexture)
|
val surface = Surface(surfaceTexture)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ import javax.microedition.khronos.opengles.GL10
|
||||||
*/
|
*/
|
||||||
class TiltShiftRenderer(
|
class TiltShiftRenderer(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val onSurfaceTextureAvailable: (SurfaceTexture) -> Unit,
|
private val onSurfaceTextureAvailable: (SurfaceTexture) -> Unit
|
||||||
private val onFrameAvailable: () -> Unit
|
|
||||||
) : GLSurfaceView.Renderer {
|
) : GLSurfaceView.Renderer {
|
||||||
|
|
||||||
private lateinit var shader: TiltShiftShader
|
private lateinit var shader: TiltShiftShader
|
||||||
|
|
@ -40,13 +39,13 @@ class TiltShiftRenderer(
|
||||||
@Volatile
|
@Volatile
|
||||||
private var isFrontCamera: Boolean = false
|
private var isFrontCamera: Boolean = false
|
||||||
|
|
||||||
// Camera resolution for aspect ratio correction (set from UI thread)
|
// Quad vertices (full screen)
|
||||||
@Volatile
|
private val vertices = floatArrayOf(
|
||||||
private var cameraWidth: Int = 0
|
-1f, -1f, // Bottom left
|
||||||
@Volatile
|
1f, -1f, // Bottom right
|
||||||
private var cameraHeight: Int = 0
|
-1f, 1f, // Top left
|
||||||
@Volatile
|
1f, 1f // Top right
|
||||||
private var vertexBufferDirty: Boolean = false
|
)
|
||||||
|
|
||||||
// Texture coordinates rotated 90° for portrait mode (back camera)
|
// Texture coordinates rotated 90° for portrait mode (back camera)
|
||||||
// (Camera sensors are landscape-oriented, we rotate to portrait)
|
// (Camera sensors are landscape-oriented, we rotate to portrait)
|
||||||
|
|
@ -76,12 +75,11 @@ class TiltShiftRenderer(
|
||||||
shader = TiltShiftShader(context)
|
shader = TiltShiftShader(context)
|
||||||
shader.initialize()
|
shader.initialize()
|
||||||
|
|
||||||
// Allocate vertex buffer (8 floats = 4 vertices × 2 components)
|
// Create vertex buffer
|
||||||
vertexBuffer = ByteBuffer.allocateDirect(8 * 4)
|
vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4)
|
||||||
.order(ByteOrder.nativeOrder())
|
.order(ByteOrder.nativeOrder())
|
||||||
.asFloatBuffer()
|
.asFloatBuffer()
|
||||||
// Fill with default full-screen quad; will be recomputed when camera resolution is known
|
.put(vertices)
|
||||||
vertexBuffer.put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f))
|
|
||||||
vertexBuffer.position(0)
|
vertexBuffer.position(0)
|
||||||
|
|
||||||
// Create texture coordinate buffer
|
// Create texture coordinate buffer
|
||||||
|
|
@ -104,7 +102,6 @@ class TiltShiftRenderer(
|
||||||
|
|
||||||
// Create SurfaceTexture for camera frames
|
// Create SurfaceTexture for camera frames
|
||||||
surfaceTexture = SurfaceTexture(cameraTextureId).also {
|
surfaceTexture = SurfaceTexture(cameraTextureId).also {
|
||||||
it.setOnFrameAvailableListener { onFrameAvailable() }
|
|
||||||
onSurfaceTextureAvailable(it)
|
onSurfaceTextureAvailable(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,19 +110,12 @@ class TiltShiftRenderer(
|
||||||
GLES20.glViewport(0, 0, width, height)
|
GLES20.glViewport(0, 0, width, height)
|
||||||
surfaceWidth = width
|
surfaceWidth = width
|
||||||
surfaceHeight = height
|
surfaceHeight = height
|
||||||
vertexBufferDirty = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDrawFrame(gl: GL10?) {
|
override fun onDrawFrame(gl: GL10?) {
|
||||||
// Update texture with latest camera frame
|
// Update texture with latest camera frame
|
||||||
surfaceTexture?.updateTexImage()
|
surfaceTexture?.updateTexImage()
|
||||||
|
|
||||||
// Recompute vertex buffer for crop-to-fill when camera or surface dimensions change
|
|
||||||
if (vertexBufferDirty) {
|
|
||||||
recomputeVertices()
|
|
||||||
vertexBufferDirty = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update texture coordinate buffer if camera changed
|
// Update texture coordinate buffer if camera changed
|
||||||
if (updateTexCoordBuffer) {
|
if (updateTexCoordBuffer) {
|
||||||
texCoordBuffer.clear()
|
texCoordBuffer.clear()
|
||||||
|
|
@ -192,53 +182,6 @@ class TiltShiftRenderer(
|
||||||
@Volatile
|
@Volatile
|
||||||
private var updateTexCoordBuffer = false
|
private var updateTexCoordBuffer = false
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the camera preview resolution for crop-to-fill aspect ratio correction.
|
|
||||||
* Thread-safe — vertex buffer is recomputed on the next frame.
|
|
||||||
*/
|
|
||||||
fun setCameraResolution(width: Int, height: Int) {
|
|
||||||
if (cameraWidth != width || cameraHeight != height) {
|
|
||||||
cameraWidth = width
|
|
||||||
cameraHeight = height
|
|
||||||
vertexBufferDirty = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recomputes 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 screen 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) {
|
|
||||||
// After 90° rotation: portrait width = cameraHeight, portrait height = cameraWidth
|
|
||||||
val cameraRatio = cameraHeight.toFloat() / cameraWidth
|
|
||||||
val screenRatio = surfaceWidth.toFloat() / surfaceHeight
|
|
||||||
|
|
||||||
if (cameraRatio > screenRatio) {
|
|
||||||
// Camera wider than screen → crop sides
|
|
||||||
scaleX = cameraRatio / screenRatio
|
|
||||||
} else {
|
|
||||||
// Camera taller than screen → crop top/bottom
|
|
||||||
scaleY = screenRatio / cameraRatio
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vertexBuffer.clear()
|
|
||||||
vertexBuffer.put(floatArrayOf(
|
|
||||||
-scaleX, -scaleY,
|
|
||||||
scaleX, -scaleY,
|
|
||||||
-scaleX, scaleY,
|
|
||||||
scaleX, scaleY
|
|
||||||
))
|
|
||||||
vertexBuffer.position(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases OpenGL resources.
|
* Releases OpenGL resources.
|
||||||
* Must be called from GL thread.
|
* Must be called from GL thread.
|
||||||
|
|
|
||||||
|
|
@ -74,8 +74,6 @@ import androidx.compose.ui.semantics.stateDescription
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
@ -118,7 +116,6 @@ fun CameraScreen(
|
||||||
val minZoom by viewModel.cameraManager.minZoomRatio.collectAsState()
|
val minZoom by viewModel.cameraManager.minZoomRatio.collectAsState()
|
||||||
val maxZoom by viewModel.cameraManager.maxZoomRatio.collectAsState()
|
val maxZoom by viewModel.cameraManager.maxZoomRatio.collectAsState()
|
||||||
val isFrontCamera by viewModel.cameraManager.isFrontCamera.collectAsState()
|
val isFrontCamera by viewModel.cameraManager.isFrontCamera.collectAsState()
|
||||||
val previewResolution by viewModel.cameraManager.previewResolution.collectAsState()
|
|
||||||
val cameraError by viewModel.cameraManager.error.collectAsState()
|
val cameraError by viewModel.cameraManager.error.collectAsState()
|
||||||
|
|
||||||
// Gallery picker
|
// Gallery picker
|
||||||
|
|
@ -164,14 +161,6 @@ fun CameraScreen(
|
||||||
glSurfaceView?.requestRender()
|
glSurfaceView?.requestRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update renderer with camera preview resolution for crop-to-fill
|
|
||||||
LaunchedEffect(previewResolution) {
|
|
||||||
if (previewResolution.width > 0) {
|
|
||||||
renderer?.setCameraResolution(previewResolution.width, previewResolution.height)
|
|
||||||
glSurfaceView?.requestRender()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start camera when surface texture is available
|
// Start camera when surface texture is available
|
||||||
LaunchedEffect(surfaceTexture) {
|
LaunchedEffect(surfaceTexture) {
|
||||||
surfaceTexture?.let {
|
surfaceTexture?.let {
|
||||||
|
|
@ -183,28 +172,14 @@ fun CameraScreen(
|
||||||
LaunchedEffect(isGalleryPreview) {
|
LaunchedEffect(isGalleryPreview) {
|
||||||
if (isGalleryPreview) {
|
if (isGalleryPreview) {
|
||||||
glSurfaceView?.onPause()
|
glSurfaceView?.onPause()
|
||||||
} else if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
} else {
|
||||||
glSurfaceView?.onResume()
|
glSurfaceView?.onResume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tie GLSurfaceView lifecycle to Activity lifecycle to prevent background rendering
|
// Cleanup GL resources on GL thread (ViewModel handles its own cleanup in onCleared)
|
||||||
val currentIsGalleryPreview by rememberUpdatedState(isGalleryPreview)
|
DisposableEffect(Unit) {
|
||||||
DisposableEffect(lifecycleOwner) {
|
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
|
||||||
when (event) {
|
|
||||||
Lifecycle.Event.ON_RESUME -> {
|
|
||||||
if (!currentIsGalleryPreview) {
|
|
||||||
glSurfaceView?.onResume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Lifecycle.Event.ON_PAUSE -> glSurfaceView?.onPause()
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lifecycleOwner.lifecycle.addObserver(observer)
|
|
||||||
onDispose {
|
onDispose {
|
||||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
glSurfaceView?.queueEvent { renderer?.release() }
|
glSurfaceView?.queueEvent { renderer?.release() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,16 +208,13 @@ fun CameraScreen(
|
||||||
GLSurfaceView(ctx).apply {
|
GLSurfaceView(ctx).apply {
|
||||||
setEGLContextClientVersion(2)
|
setEGLContextClientVersion(2)
|
||||||
|
|
||||||
val view = this
|
val newRenderer = TiltShiftRenderer(ctx) { st ->
|
||||||
val newRenderer = TiltShiftRenderer(
|
surfaceTexture = st
|
||||||
context = ctx,
|
}
|
||||||
onSurfaceTextureAvailable = { st -> surfaceTexture = st },
|
|
||||||
onFrameAvailable = { view.requestRender() }
|
|
||||||
)
|
|
||||||
renderer = newRenderer
|
renderer = newRenderer
|
||||||
|
|
||||||
setRenderer(newRenderer)
|
setRenderer(newRenderer)
|
||||||
renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
|
||||||
|
|
||||||
glSurfaceView = this
|
glSurfaceView = this
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Bumps the version in version.properties before a release build.
|
|
||||||
# Usage: ./bump-version.sh [major|minor|patch]
|
|
||||||
# Default: patch
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
PROPS_FILE="$(dirname "$0")/version.properties"
|
|
||||||
|
|
||||||
if [[ ! -f "$PROPS_FILE" ]]; then
|
|
||||||
echo "Error: $PROPS_FILE not found" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Read current values
|
|
||||||
major=$(grep '^versionMajor=' "$PROPS_FILE" | cut -d= -f2)
|
|
||||||
minor=$(grep '^versionMinor=' "$PROPS_FILE" | cut -d= -f2)
|
|
||||||
patch=$(grep '^versionPatch=' "$PROPS_FILE" | cut -d= -f2)
|
|
||||||
code=$(grep '^versionCode=' "$PROPS_FILE" | cut -d= -f2)
|
|
||||||
|
|
||||||
bump_type="${1:-patch}"
|
|
||||||
|
|
||||||
case "$bump_type" in
|
|
||||||
major)
|
|
||||||
major=$((major + 1))
|
|
||||||
minor=0
|
|
||||||
patch=0
|
|
||||||
;;
|
|
||||||
minor)
|
|
||||||
minor=$((minor + 1))
|
|
||||||
patch=0
|
|
||||||
;;
|
|
||||||
patch)
|
|
||||||
patch=$((patch + 1))
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 [major|minor|patch]" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
code=$((code + 1))
|
|
||||||
|
|
||||||
# Write updated values
|
|
||||||
cat > "$PROPS_FILE" << EOF
|
|
||||||
versionMajor=$major
|
|
||||||
versionMinor=$minor
|
|
||||||
versionPatch=$patch
|
|
||||||
versionCode=$code
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "$major.$minor.$patch (versionCode=$code)"
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
versionMajor=1
|
|
||||||
versionMinor=1
|
|
||||||
versionPatch=1
|
|
||||||
versionCode=3
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue