Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand All @@ -295,7 +293,7 @@ class CameraXPreview(
.setFlashMode(flashMode)
.setJpegQuality(config.photoQuality)
.setTargetRotation(rotation)
.setTargetResolution(resolution)
.setResolutionSelector(getResolutionSelector(resolution))
.build()
}

Expand All @@ -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<Recorder> {
val qualitySelector = QualitySelector.from(
videoQualityManager.getUserSelectedQuality(cameraSelector).toCameraXQuality(),
Expand Down
Loading