diff --git a/CHANGELOG.md b/CHANGELOG.md index 35fed9cd..d4284973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Support high resolution output modes if the config preference is set to maximize quality. ([#20]) ## [1.1.0] - 2025-07-21 ### Changed @@ -39,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release +[#20]: https://github.com/FossifyOrg/Camera/issues/20 [#97]: https://github.com/FossifyOrg/Camera/issues/97 [Unreleased]: https://github.com/FossifyOrg/Camera/compare/1.1.0...HEAD diff --git a/app/src/main/kotlin/org/fossify/camera/helpers/ImageQualityManager.kt b/app/src/main/kotlin/org/fossify/camera/helpers/ImageQualityManager.kt index f7d77348..85b2818d 100644 --- a/app/src/main/kotlin/org/fossify/camera/helpers/ImageQualityManager.kt +++ b/app/src/main/kotlin/org/fossify/camera/helpers/ImageQualityManager.kt @@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.camera.core.CameraSelector import org.fossify.camera.extensions.config import org.fossify.camera.models.CameraSelectorImageQualities +import org.fossify.camera.models.CaptureMode import org.fossify.camera.models.MySize import org.fossify.commons.extensions.showErrorToast @@ -32,8 +33,14 @@ class ImageQualityManager(private val activity: AppCompatActivity) { val configMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue - val imageSizes = configMap.getOutputSizes(ImageFormat.JPEG) + val standardImageSizes = configMap.getOutputSizes(ImageFormat.JPEG) .map { MySize(it.width, it.height) } + val imageSizes = if (activity.config.captureMode == CaptureMode.MAXIMIZE_QUALITY) { + standardImageSizes + configMap.getHighResolutionOutputSizes(ImageFormat.JPEG) + .map { MySize(it.width, it.height) } + } else { + standardImageSizes + } val cameraSelector = lensFacing.toCameraSelector() imageQualities.add(CameraSelectorImageQualities(cameraSelector, imageSizes)) } 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 fbdd7c80..48b4b323 100644 --- a/app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreview.kt +++ b/app/src/main/kotlin/org/fossify/camera/implementations/CameraXPreview.kt @@ -36,6 +36,11 @@ import androidx.camera.core.Preview import androidx.camera.core.UseCase import androidx.camera.core.UseCaseGroup import androidx.camera.core.ViewPort +import androidx.camera.core.resolutionselector.ResolutionSelector +import androidx.camera.core.resolutionselector.ResolutionSelector.PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION +import androidx.camera.core.resolutionselector.ResolutionSelector.PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE +import androidx.camera.core.resolutionselector.ResolutionStrategy +import androidx.camera.core.resolutionselector.ResolutionStrategy.FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.FallbackStrategy import androidx.camera.video.FileDescriptorOutputOptions @@ -86,6 +91,7 @@ import org.fossify.commons.activities.BaseSimpleActivity import org.fossify.commons.extensions.toast import org.fossify.commons.helpers.PERMISSION_ACCESS_FINE_LOCATION import org.fossify.commons.helpers.ensureBackgroundThread +import kotlin.math.abs class CameraXPreview( private val activity: BaseSimpleActivity, @@ -222,10 +228,10 @@ class CameraXPreview( val isFullSize = resolution.isFullScreen previewView.scaleType = if (isFullSize) ScaleType.FILL_CENTER else ScaleType.FIT_CENTER val rotation = previewView.display.rotation - val rotatedResolution = getRotatedResolution(resolution, rotation) + val targetResolution = Size(resolution.width, resolution.height) - val previewUseCase = buildPreview(rotatedResolution, rotation) - val captureUseCase = getCaptureUseCase(rotatedResolution, rotation) + val previewUseCase = buildPreview(targetResolution, rotation) + val captureUseCase = getCaptureUseCase(targetResolution, rotation) cameraProvider.unbindAll() camera = if (isFullSize) { @@ -258,18 +264,10 @@ class CameraXPreview( setFlashlightState(config.flashlightState) } - private fun getRotatedResolution(resolution: MySize, rotationDegrees: Int): Size { - return if (rotationDegrees == Surface.ROTATION_0 || rotationDegrees == Surface.ROTATION_180) { - Size(resolution.height, resolution.width) - } else { - Size(resolution.width, resolution.height) - } - } - private fun buildPreview(resolution: Size, rotation: Int): Preview { return Preview.Builder() .setTargetRotation(rotation) - .setTargetResolution(resolution) + .setResolutionSelector(getResolutionSelector(resolution)) .build().apply { setSurfaceProvider(previewView.surfaceProvider) } @@ -295,7 +293,7 @@ class CameraXPreview( .setFlashMode(flashMode) .setJpegQuality(config.photoQuality) .setTargetRotation(rotation) - .setTargetResolution(resolution) + .setResolutionSelector(getResolutionSelector(resolution)) .build() } @@ -306,6 +304,26 @@ class CameraXPreview( } } + private fun getResolutionSelector(resolution: Size): ResolutionSelector { + return ResolutionSelector.Builder() + .setResolutionStrategy(ResolutionStrategy(resolution, FALLBACK_RULE_CLOSEST_LOWER_THEN_HIGHER)) + .setResolutionFilter { supportedSizes, rotationDegrees -> + // Sort by closest image ratio + supportedSizes.sortedBy { + size -> abs(size.width / size.height.toFloat() - resolution.width / resolution.height.toFloat()) + } + } + .setAllowedResolutionMode(getAllowedResolutionMode()) + .build() + } + + private fun getAllowedResolutionMode(): Int { + return when (config.captureMode) { + CaptureMode.MINIMIZE_LATENCY -> PREFER_CAPTURE_RATE_OVER_HIGHER_RESOLUTION + CaptureMode.MAXIMIZE_QUALITY -> PREFER_HIGHER_RESOLUTION_OVER_CAPTURE_RATE + } + } + private fun buildVideoCapture(): VideoCapture { val qualitySelector = QualitySelector.from( videoQualityManager.getUserSelectedQuality(cameraSelector).toCameraXQuality(),