Merge pull request #335 from KryptKode/feat/camera-x
adjust preview based on selected aspect ratio
This commit is contained in:
commit
d2878d294a
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue