mirror of
				https://github.com/SimpleMobileTools/Simple-Camera.git
				synced 2025-06-27 09:02:59 +02:00 
			
		
		
		
	Merge pull request #335 from KryptKode/feat/camera-x
adjust preview based on selected aspect ratio
This commit is contained in:
		| @@ -50,8 +50,13 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera | ||||
|     private var mCurrVideoRecTimer = 0 | ||||
|     var mLastHandledOrientation = 0 | ||||
|  | ||||
|     @Suppress("DEPRECATION") | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         window.addFlags( | ||||
|             WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or | ||||
|                 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or | ||||
|                 WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or | ||||
|                 WindowManager.LayoutParams.FLAG_FULLSCREEN | ||||
|         ) | ||||
|         useDynamicTheme = false | ||||
|         super.onCreate(savedInstanceState) | ||||
|         appLaunched(BuildConfig.APPLICATION_ID) | ||||
| @@ -62,22 +67,6 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera | ||||
|         supportActionBar?.hide() | ||||
|         checkWhatsNewDialog() | ||||
|         setupOrientationEventListener() | ||||
|         if (isRPlus()) { | ||||
|             setShowWhenLocked(true) | ||||
|             setTurnScreenOn(true) | ||||
|             window.insetsController?.hide(WindowInsets.Type.statusBars()) | ||||
|         } else if (isOreoMr1Plus()) { | ||||
|             setShowWhenLocked(true) | ||||
|             setTurnScreenOn(true) | ||||
|             window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) | ||||
|         } else { | ||||
|             window.addFlags( | ||||
|                 WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or | ||||
|                     WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or | ||||
|                     WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or | ||||
|                     WindowManager.LayoutParams.FLAG_FULLSCREEN | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onResume() { | ||||
|   | ||||
| @@ -15,8 +15,6 @@ const val FLASHLIGHT_STATE = "flashlight_state" | ||||
| const val INIT_PHOTO_MODE = "init_photo_mode" | ||||
| const val BACK_PHOTO_RESOLUTION_INDEX = "back_photo_resolution_index_2" | ||||
| const val BACK_VIDEO_RESOLUTION_INDEX = "back_video_resolution_index_2" | ||||
| const val BACK_VIDEO_QUALITY = "back_video_quality_2" | ||||
| const val FRONT_VIDEO_QUALITY = "front_video_quality_2" | ||||
| const val FRONT_PHOTO_RESOLUTION_INDEX = "front_photo_resolution_index_2" | ||||
| const val FRONT_VIDEO_RESOLUTION_INDEX = "front_video_resolution_index_2" | ||||
| const val KEEP_SETTINGS_VISIBLE = "keep_settings_visible" | ||||
|   | ||||
| @@ -4,9 +4,6 @@ import android.content.Context | ||||
| import android.graphics.ImageFormat | ||||
| import android.hardware.camera2.CameraCharacteristics | ||||
| import android.hardware.camera2.CameraManager | ||||
| import android.hardware.camera2.params.StreamConfigurationMap | ||||
| import android.media.MediaRecorder | ||||
| import android.util.Size | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.camera.core.CameraSelector | ||||
| import com.simplemobiletools.camera.extensions.config | ||||
| @@ -19,8 +16,6 @@ class ImageQualityManager( | ||||
| ) { | ||||
|  | ||||
|     companion object { | ||||
|         private const val MAX_VIDEO_WIDTH = 4096 | ||||
|         private const val MAX_VIDEO_HEIGHT = 2160 | ||||
|         private val CAMERA_LENS = arrayOf(CameraCharacteristics.LENS_FACING_FRONT, CameraCharacteristics.LENS_FACING_BACK) | ||||
|     } | ||||
|  | ||||
| @@ -29,29 +24,25 @@ class ImageQualityManager( | ||||
|     private val imageQualities = mutableListOf<CameraSelectorImageQualities>() | ||||
|  | ||||
|     fun initSupportedQualities() { | ||||
|         for (cameraId in cameraManager.cameraIdList) { | ||||
|             try { | ||||
|                 val characteristics = cameraManager.getCameraCharacteristics(cameraId) | ||||
|                 for (lens in CAMERA_LENS) { | ||||
|                     if (characteristics.get(CameraCharacteristics.LENS_FACING) == lens) { | ||||
|                         val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue | ||||
|                         val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) } | ||||
|                         val cameraSelector = lens.toCameraSelector() | ||||
|                         imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes)) | ||||
|         if (imageQualities.isEmpty()) { | ||||
|             for (cameraId in cameraManager.cameraIdList) { | ||||
|                 try { | ||||
|                     val characteristics = cameraManager.getCameraCharacteristics(cameraId) | ||||
|                     for (lens in CAMERA_LENS) { | ||||
|                         if (characteristics.get(CameraCharacteristics.LENS_FACING) == lens) { | ||||
|                             val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue | ||||
|                             val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) } | ||||
|                             val cameraSelector = lens.toCameraSelector() | ||||
|                             imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes)) | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (e: Exception) { | ||||
|                     activity.showErrorToast(e) | ||||
|                 } | ||||
|             } catch (e: Exception) { | ||||
|                 activity.showErrorToast(e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun getAvailableVideoSizes(configMap: StreamConfigurationMap): List<Size> { | ||||
|         return configMap.getOutputSizes(MediaRecorder::class.java).filter { | ||||
|             it.width <= MAX_VIDEO_WIDTH && it.height <= MAX_VIDEO_HEIGHT | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun Int.toCameraSelector(): CameraSelector { | ||||
|         return if (this == CameraCharacteristics.LENS_FACING_FRONT) { | ||||
|             CameraSelector.DEFAULT_FRONT_CAMERA | ||||
| @@ -60,20 +51,19 @@ class ImageQualityManager( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getUserSelectedResolution(cameraSelector: CameraSelector): Size? { | ||||
|     fun getUserSelectedResolution(cameraSelector: CameraSelector): MySize { | ||||
|         val index = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontPhotoResIndex else config.backPhotoResIndex | ||||
|         return imageQualities.filter { it.camSelector == cameraSelector } | ||||
|             .flatMap { it.qualities } | ||||
|             .sortedByDescending { it.pixels } | ||||
|             .sortedWith(compareByDescending<MySize> { it.ratio }.thenByDescending { it.pixels }) | ||||
|             .distinctBy { it.pixels } | ||||
|             .map { Size(it.width, it.height) } | ||||
|             .getOrNull(index) | ||||
|             .filter { it.megaPixels != "0.0" }[index] | ||||
|     } | ||||
|  | ||||
|     fun getSupportedResolutions(cameraSelector: CameraSelector): List<MySize> { | ||||
|         return imageQualities.filter { it.camSelector == cameraSelector } | ||||
|             .flatMap { it.qualities } | ||||
|             .sortedByDescending { it.pixels } | ||||
|             .sortedWith(compareByDescending<MySize> { it.ratio }.thenByDescending { it.pixels }) | ||||
|             .distinctBy { it.pixels } | ||||
|             .filter { it.megaPixels != "0.0" } | ||||
|     } | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import androidx.camera.lifecycle.ProcessCameraProvider | ||||
| import androidx.camera.video.Quality | ||||
| import androidx.camera.video.QualitySelector | ||||
| import com.simplemobiletools.camera.extensions.config | ||||
| import com.simplemobiletools.camera.extensions.toCameraXQuality | ||||
| import com.simplemobiletools.camera.extensions.toVideoQuality | ||||
| import com.simplemobiletools.camera.models.CameraSelectorVideoQualities | ||||
| import com.simplemobiletools.camera.models.VideoQuality | ||||
| @@ -45,10 +44,10 @@ class VideoQualityManager( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getUserSelectedQuality(cameraSelector: CameraSelector): Quality { | ||||
|     fun getUserSelectedQuality(cameraSelector: CameraSelector): VideoQuality { | ||||
|         var selectionIndex = if (cameraSelector == CameraSelector.DEFAULT_FRONT_CAMERA) config.frontVideoResIndex else config.backVideoResIndex | ||||
|         selectionIndex = selectionIndex.coerceAtLeast(0) | ||||
|         return getSupportedQualities(cameraSelector).getOrElse(selectionIndex) { VideoQuality.HD }.toCameraXQuality() | ||||
|         return getSupportedQualities(cameraSelector).getOrElse(selectionIndex) { VideoQuality.HD } | ||||
|     } | ||||
|  | ||||
|     fun getSupportedQualities(cameraSelector: CameraSelector): List<VideoQuality> { | ||||
|   | ||||
| @@ -18,7 +18,6 @@ import androidx.core.content.ContextCompat | ||||
| import androidx.core.view.doOnLayout | ||||
| import androidx.lifecycle.DefaultLifecycleObserver | ||||
| import androidx.lifecycle.LifecycleOwner | ||||
| import androidx.window.layout.WindowMetricsCalculator | ||||
| import com.bumptech.glide.load.ImageHeaderParser.UNKNOWN_ORIENTATION | ||||
| import com.simplemobiletools.camera.R | ||||
| import com.simplemobiletools.camera.dialogs.ChangeResolutionDialogX | ||||
| @@ -26,11 +25,9 @@ import com.simplemobiletools.camera.extensions.* | ||||
| import com.simplemobiletools.camera.helpers.* | ||||
| import com.simplemobiletools.camera.interfaces.MyPreview | ||||
| import com.simplemobiletools.camera.models.MediaOutput | ||||
| import com.simplemobiletools.camera.models.MySize | ||||
| import com.simplemobiletools.commons.extensions.toast | ||||
| import com.simplemobiletools.commons.helpers.ensureBackgroundThread | ||||
| import kotlin.math.abs | ||||
| import kotlin.math.max | ||||
| import kotlin.math.min | ||||
|  | ||||
| class CameraXPreview( | ||||
|     private val activity: AppCompatActivity, | ||||
| @@ -42,9 +39,6 @@ class CameraXPreview( | ||||
| ) : MyPreview, DefaultLifecycleObserver { | ||||
|  | ||||
|     companion object { | ||||
|         private const val RATIO_4_3_VALUE = 4.0 / 3.0 | ||||
|         private const val RATIO_16_9_VALUE = 16.0 / 9.0 | ||||
|  | ||||
|         // Auto focus is 1/6 of the area. | ||||
|         private const val AF_SIZE = 1.0f / 6.0f | ||||
|         private const val AE_SIZE = AF_SIZE * 1.5f | ||||
| @@ -55,7 +49,6 @@ class CameraXPreview( | ||||
|     private val mainExecutor = ContextCompat.getMainExecutor(activity) | ||||
|     private val displayManager = activity.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager | ||||
|     private val mediaSoundHelper = MediaSoundHelper() | ||||
|     private val windowMetricsCalculator = WindowMetricsCalculator.getOrCreate() | ||||
|     private val videoQualityManager = VideoQualityManager(activity) | ||||
|     private val imageQualityManager = ImageQualityManager(activity) | ||||
|     private val exifRemover = ExifRemover(contentResolver) | ||||
| @@ -73,9 +66,13 @@ class CameraXPreview( | ||||
|                 in 225 until 315 -> Surface.ROTATION_90 | ||||
|                 else -> Surface.ROTATION_0 | ||||
|             } | ||||
|             preview?.targetRotation = rotation | ||||
|             imageCapture?.targetRotation = rotation | ||||
|             videoCapture?.targetRotation = rotation | ||||
|  | ||||
|             if (lastRotation != rotation) { | ||||
|                 preview?.targetRotation = rotation | ||||
|                 imageCapture?.targetRotation = rotation | ||||
|                 videoCapture?.targetRotation = rotation | ||||
|                 lastRotation = rotation | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -89,6 +86,7 @@ class CameraXPreview( | ||||
|     private var cameraSelector = config.lastUsedCameraLens.toCameraSelector() | ||||
|     private var flashMode = FLASH_MODE_OFF | ||||
|     private var isPhotoCapture = initInPhotoMode | ||||
|     private var lastRotation = 0 | ||||
|  | ||||
|     init { | ||||
|         bindToLifeCycle() | ||||
| @@ -122,17 +120,18 @@ class CameraXPreview( | ||||
|  | ||||
|     private fun bindCameraUseCases() { | ||||
|         val cameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.") | ||||
|         val metrics = windowMetricsCalculator.computeCurrentWindowMetrics(activity).bounds | ||||
|         val aspectRatio = if (isPhotoCapture) { | ||||
|             aspectRatio(metrics.width(), metrics.height()) | ||||
|         val rotation = previewView.display.rotation | ||||
|         val resolution = if (isPhotoCapture) { | ||||
|             imageQualityManager.getUserSelectedResolution(cameraSelector) | ||||
|         } else { | ||||
|             val selectedQuality = videoQualityManager.getUserSelectedQuality(cameraSelector) | ||||
|             selectedQuality.getAspectRatio() | ||||
|             MySize(selectedQuality.width, selectedQuality.height) | ||||
|         } | ||||
|         val rotation = previewView.display.rotation | ||||
|  | ||||
|         preview = buildPreview(aspectRatio, rotation) | ||||
|         val captureUseCase = getCaptureUseCase(aspectRatio, rotation) | ||||
|         val rotatedResolution = getRotatedResolution(resolution, rotation) | ||||
|  | ||||
|         preview = buildPreview(rotatedResolution, rotation) | ||||
|         val captureUseCase = getCaptureUseCase(rotatedResolution, rotation) | ||||
|         cameraProvider.unbindAll() | ||||
|         camera = cameraProvider.bindToLifecycle( | ||||
|             activity, | ||||
| @@ -167,10 +166,10 @@ class CameraXPreview( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun getCaptureUseCase(aspectRatio: Int, rotation: Int): UseCase { | ||||
|     private fun getCaptureUseCase(resolution: Size, rotation: Int): UseCase { | ||||
|         return if (isPhotoCapture) { | ||||
|             cameraProvider?.unbind(videoCapture) | ||||
|             buildImageCapture(aspectRatio, rotation).also { | ||||
|             buildImageCapture(resolution, rotation).also { | ||||
|                 imageCapture = it | ||||
|             } | ||||
|         } else { | ||||
| @@ -181,22 +180,17 @@ class CameraXPreview( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun buildImageCapture(aspectRatio: Int, rotation: Int): ImageCapture { | ||||
|     private fun buildImageCapture(resolution: Size, rotation: Int): ImageCapture { | ||||
|         return Builder() | ||||
|             .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY) | ||||
|             .setFlashMode(flashMode) | ||||
|             .setJpegQuality(config.photoQuality) | ||||
|             .setTargetRotation(rotation) | ||||
|             .apply { | ||||
|                 imageQualityManager.getUserSelectedResolution(cameraSelector)?.let { resolution -> | ||||
|                     val rotatedResolution = getRotatedResolution(rotation, resolution) | ||||
|                     setTargetResolution(rotatedResolution) | ||||
|                 } ?: setTargetAspectRatio(aspectRatio) | ||||
|             } | ||||
|             .setTargetResolution(resolution) | ||||
|             .build() | ||||
|     } | ||||
|  | ||||
|     private fun getRotatedResolution(rotationDegrees: Int, resolution: Size): Size { | ||||
|     private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): Size { | ||||
|         return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) { | ||||
|             Size(resolution.height, resolution.width) | ||||
|         } else { | ||||
| @@ -204,16 +198,16 @@ class CameraXPreview( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun buildPreview(aspectRatio: Int, rotation: Int): Preview { | ||||
|     private fun buildPreview(resolution: Size, rotation: Int): Preview { | ||||
|         return Preview.Builder() | ||||
|             .setTargetRotation(rotation) | ||||
|             .setTargetAspectRatio(aspectRatio) | ||||
|             .setTargetResolution(resolution) | ||||
|             .build() | ||||
|     } | ||||
|  | ||||
|     private fun buildVideoCapture(): VideoCapture<Recorder> { | ||||
|         val qualitySelector = QualitySelector.from( | ||||
|             videoQualityManager.getUserSelectedQuality(cameraSelector), | ||||
|             videoQualityManager.getUserSelectedQuality(cameraSelector).toCameraXQuality(), | ||||
|             FallbackStrategy.higherQualityOrLowerThan(Quality.SD), | ||||
|         ) | ||||
|         val recorder = Recorder.Builder() | ||||
| @@ -222,14 +216,6 @@ class CameraXPreview( | ||||
|         return VideoCapture.withOutput(recorder) | ||||
|     } | ||||
|  | ||||
|     private fun aspectRatio(width: Int, height: Int): Int { | ||||
|         val previewRatio = max(width, height).toDouble() / min(width, height) | ||||
|         if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) { | ||||
|             return AspectRatio.RATIO_4_3 | ||||
|         } | ||||
|         return AspectRatio.RATIO_16_9 | ||||
|     } | ||||
|  | ||||
|     private fun hasBackCamera(): Boolean { | ||||
|         return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false | ||||
|     } | ||||
| @@ -288,7 +274,7 @@ class CameraXPreview( | ||||
|             activity, | ||||
|             isFrontCameraInUse(), | ||||
|             imageQualityManager.getSupportedResolutions(cameraSelector), | ||||
|             videoQualityManager.getSupportedQualities(cameraSelector) | ||||
|             videoQualityManager.getSupportedQualities(cameraSelector), | ||||
|         ) { | ||||
|             if (oldQuality != videoQualityManager.getUserSelectedQuality(cameraSelector)) { | ||||
|                 currentRecording?.stop() | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:id="@+id/view_holder" | ||||
|     android:layout_width="match_parent" | ||||
| @@ -9,7 +10,8 @@ | ||||
|     <androidx.camera.view.PreviewView | ||||
|         android:id="@+id/preview_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" /> | ||||
|         android:layout_height="match_parent" | ||||
|         app:scaleType="fitStart" /> | ||||
|  | ||||
|     <View | ||||
|         android:id="@+id/capture_black_screen" | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:id="@+id/change_resolution_holder" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
| @@ -20,17 +19,15 @@ | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_centerVertical="true" | ||||
|             android:layout_toStartOf="@+id/change_resolution_photo" | ||||
|             android:layout_toLeftOf="@+id/change_resolution_photo" | ||||
|             android:text="@string/photo"/> | ||||
|             android:text="@string/photo" /> | ||||
|  | ||||
|         <com.simplemobiletools.commons.views.MyTextView | ||||
|             android:id="@+id/change_resolution_photo" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_alignParentEnd="true" | ||||
|             android:layout_alignParentRight="true" | ||||
|             android:background="@null" | ||||
|             android:clickable="false"/> | ||||
|             android:clickable="false" /> | ||||
|  | ||||
|     </RelativeLayout> | ||||
|  | ||||
| @@ -48,17 +45,15 @@ | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_centerVertical="true" | ||||
|             android:layout_toStartOf="@+id/change_resolution_video" | ||||
|             android:layout_toLeftOf="@+id/change_resolution_video" | ||||
|             android:text="@string/video"/> | ||||
|             android:text="@string/video" /> | ||||
|  | ||||
|         <com.simplemobiletools.commons.views.MyTextView | ||||
|             android:id="@+id/change_resolution_video" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_alignParentEnd="true" | ||||
|             android:layout_alignParentRight="true" | ||||
|             android:background="@null" | ||||
|             android:clickable="false"/> | ||||
|             android:clickable="false" /> | ||||
|  | ||||
|     </RelativeLayout> | ||||
| </LinearLayout> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user