From 9ad6d67069605150d8e7d599572568675a6b63bc Mon Sep 17 00:00:00 2001 From: Philipp Hasper Date: Thu, 30 Apr 2026 22:02:29 +0200 Subject: [PATCH] fix: handle device rotations from before app start Previously, both images and videos where stored with the wrong orientation if you had rotated the phone to landscape before opening the app. Only rotations while the camera app is open were handled by the listener. The target rotation also needs to be applied immediately at bind time, to correctly react to rotations that happened before the app lifecycle. --- CHANGELOG.md | 3 +++ .../camera/implementations/CameraXPreview.kt | 25 +++++++++++-------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dee8610a..2a80c884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Fixed wrongly oriented photos and videos when opening in landscape ([#294]) + ## [1.5.0] - 2026-01-30 ### Added - Added support for custom fonts diff --git a/app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreview.kt b/app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreview.kt index 48b4b323..85b674d0 100644 --- a/app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreview.kt @@ -105,7 +105,7 @@ class CameraXPreview( ) : MyPreview, DefaultLifecycleObserver { companion object { - // Auto focus is 1/6 of the area. + // Autofocus is 1/6 of the area. private const val AF_SIZE = 1.0f / 6.0f private const val AE_SIZE = AF_SIZE * 1.5f private const val CAMERA_MODE_SWITCH_WAIT_TIME = 500L @@ -136,11 +136,9 @@ class CameraXPreview( else -> Surface.ROTATION_0 } - if (lastRotation != rotation) { - preview?.targetRotation = rotation - imageCapture?.targetRotation = rotation - videoCapture?.targetRotation = rotation - lastRotation = rotation + if (targetRotation != rotation) { + targetRotation = rotation + applyCaptureRotation(rotation) } } } @@ -178,7 +176,7 @@ class CameraXPreview( private var cameraSelector = config.lastUsedCameraLens.toCameraSelector() private var flashMode = FLASH_MODE_OFF private var isPhotoCapture = initInPhotoMode - private var lastRotation = 0 + private var targetRotation: Int? = null private var lastCameraStartTime = 0L private var simpleLocationManager: SimpleLocationManager? = null @@ -200,7 +198,7 @@ class CameraXPreview( videoQualityManager.initSupportedQualities(provider) bindCameraUseCases() setupCameraObservers() - } catch (e: Exception) { + } catch (_: Exception) { val errorMessage = if (switching) R.string.camera_switch_error else R.string.camera_open_error activity.toast(errorMessage) @@ -260,16 +258,22 @@ class CameraXPreview( ) } preview = previewUseCase + applyCaptureRotation(targetRotation ?: rotation) setupZoomAndFocus() setFlashlightState(config.flashlightState) } + private fun applyCaptureRotation(rotation: Int) { + imageCapture?.targetRotation = rotation + videoCapture?.targetRotation = rotation + } + private fun buildPreview(resolution: Size, rotation: Int): Preview { return Preview.Builder() .setTargetRotation(rotation) .setResolutionSelector(getResolutionSelector(resolution)) .build().apply { - setSurfaceProvider(previewView.surfaceProvider) + surfaceProvider = previewView.surfaceProvider } } @@ -307,7 +311,7 @@ class CameraXPreview( private fun getResolutionSelector(resolution: Size): ResolutionSelector { return ResolutionSelector.Builder() .setResolutionStrategy(ResolutionStrategy(resolution, FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER)) - .setResolutionFilter { supportedSizes, rotationDegrees -> + .setResolutionFilter { supportedSizes, _ -> // Sort by closest image ratio supportedSizes.sortedBy { size -> abs(size.width / size.height.toFloat() - resolution.width / resolution.height.toFloat()) @@ -317,6 +321,7 @@ class CameraXPreview( .build() } + private fun getAllowedResolutionMode(): Int { return when (config.captureMode) { CaptureMode.MINIMIZE_LATENCY -> PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION