How to set a correct aspect ratio using CameraX? - android

I'm following the CameraX codelab and I'm getting a wrong aspect ratio on the preview even using setTargetAspectRatio and setTargetResolution methods.
private fun startCamera() {
// Create configuration object for the viewfinder use case
val previewConfig = PreviewConfig.Builder().apply {
setTargetAspectRatio(Rational(1, 1))
setTargetResolution(Size(640, 640))
}.build()
...
And the layout is using a hardcoded size as presented in the codelab.
<TextureView
android:id="#+id/view_finder"
android:layout_width="640px"
android:layout_height="640px"
...
It would be nice if the library had CameraTextureView and a property android:scaleType (similar to the existing for the ImageView) to adjust the preview to the preview size.

did you try it?
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
val screenSize = Size(metrics.widthPixels, metrics.heightPixels)
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val viewFinderConfig = PreviewConfig.Builder().apply {
//...
setTargetResolution(screenSize)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(viewFinder.display.rotation)
}.build()
val preview = AutoFitPreviewBuilder.build(viewFinderConfig, viewFinder)
And AutoFitPreviewBuilder you can find here:
https://gist.github.com/yevhenRoman/90681822adef43350844464be95d23f1
I would recommend you to set width and height for your TextureView using dp or constaraints.
Let me know if it works for you, thanks!

https://stackoverflow.com/a/49449986/9397052 there is a solution for a similar problem. After you decide on a resolution, don't use setTargetAspectRatio function; instead you should only use setTargetResolution. Then it should work the same.

According to the official Android documentation:
"It is not allowed to set both target aspect ratio and target resolution on the same use case."
Although the compiler does not throw an error if you attempt to do this, unexpected results can occur.

You can set the aspect ratio of a TextureView with Matrix like this:
Matrix txform = new Matrix();
textureView.getTransform(txform);
txform.setScale((float) = videoWidth / viewWidth, (float) videoHeight / viewHeight);// aspect ratio
textureView.setTransform(txform); // apply matrix

Related

How to increase image quality with android cameraX library?

I am creating an application which must implement its own camera.
I use the cameraX library provided by google.
I noticed that there is a difference between the quality of the image captured by my own application, and the image captured by the camera application installed on my phone.
although the 2 photos are captured with the same conditions (light, position...)
especially when I zoom the photo, the details of the image become more blurry to the image captured by my application
(in my own case, my phone is Google Pixel 5)
Please see these 2 photos to see the difference
Image by phone camera
Image by my app
And this is my code
/**
* Initialize CameraX, and prepare to bind the camera use cases
*/
private fun setupCamera()
{
val cameraProviderFuture : ListenableFuture<ProcessCameraProvider> = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
cameraProvider = cameraProviderFuture.get()
lensFacing = when
{
hasBackCamera() -> CameraSelector.LENS_FACING_BACK
hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
else -> throw IllegalStateException("Back and front camera are unavailable")
}
bindCameraUseCases()
setupCameraGestures()
}, ContextCompat.getMainExecutor(this))
}
/**
* Declare and bind preview, capture and analysis use cases.
*/
private fun bindCameraUseCases()
{
lifecycleScope.launch {
val cameraProvider : ProcessCameraProvider = cameraProvider ?: throw IllegalStateException("Camera initialization failed.")
// Try to apply extensions like HDR, NIGHT ##########################################
val extensionsManager : ExtensionsManager = ExtensionsManager.getInstanceAsync(this#ImageCaptureActivity, cameraProvider).await()
val defaultCameraSelector : CameraSelector = CameraSelector.Builder()
.requireLensFacing(lensFacing)
.build()
val finalCameraSelector : CameraSelector = if (extensionsManager.isExtensionAvailable(defaultCameraSelector, ExtensionMode.AUTO))
{
extensionsManager.getExtensionEnabledCameraSelector(defaultCameraSelector, ExtensionMode.AUTO)
}
else
{
defaultCameraSelector
}
// Get screen metrics used to setup camera for full screen resolution
val metrics : DisplayMetrics = resources.displayMetrics
val screenAspectRatio : Int = aspectRatio(metrics.widthPixels, metrics.heightPixels)
val rotation : Int = binding.cameraPreview.display.rotation
preview = Preview.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation
.setTargetRotation(rotation)
.build()
imageCapture = ImageCapture.Builder()
// We request aspect ratio but no resolution to match preview config, but letting
// CameraX optimize for whatever specific resolution best fits our use cases
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.setJpegQuality(100)
.build()
imageAnalyzer = ImageAnalysis.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
.build()
imageAnalyzer?.setAnalyzer(cameraExecutor, LuminosityAnalyzer {})
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try
{
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl & CameraInfo
camera = cameraProvider.bindToLifecycle(this#ImageCaptureActivity, finalCameraSelector, preview, imageCapture, imageAnalyzer)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(binding.cameraPreview.surfaceProvider)
}
catch (exception : Exception)
{
exception.printStackTrace()
}
}
}
/**
* [androidx.camera.core.ImageAnalysisConfig] requires enum value of [androidx.camera.core.AspectRatio].
* Currently it has values of 4:3 & 16:9.
*
* Detecting the most suitable ratio for dimensions provided in #params by counting absolute
* of preview ratio to one of the provided values.
*
* #param width - preview width
* #param height - preview height
* #return suitable aspect ratio
*/
private fun aspectRatio(width : Int, height : Int) : Int
{
val previewRatio : Double = max(width, height).toDouble() / min(width, height)
return if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE))
{
AspectRatio.RATIO_4_3
}
else
{
AspectRatio.RATIO_16_9
}
}
fun captureImage()
{
if (!permissionsOk()) return
// Get a stable reference of the modifiable image capture use case
imageCapture?.let { imageCapture ->
// Create output file to hold the image
val photoFile : File = storageUtils.createFile(
baseFolder = getOutputPath(),
fileName = System.currentTimeMillis().toString(),
fileExtension = StorageUtils.PHOTO_EXTENSION)
// Setup image capture metadata
val metadata : Metadata = Metadata().also {
// Mirror image when using the front camera
it.isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
it.location = locationManager.lastKnownLocation
}
// Create output options object which contains file + metadata
val outputOptions : ImageCapture.OutputFileOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
.setMetadata(metadata)
.build()
imagesAdapter.addImage(photoFile)
// Setup image capture listener which is triggered after photo has been taken
imageCapture.takePicture(outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback
{
override fun onImageSaved(output : ImageCapture.OutputFileResults)
{
val savedUri : Uri = output.savedUri ?: return
StorageUtils.showInGallery(savedUri.path)
binding.list.post {
imagesAdapter.addImage(savedUri.toFile())
binding.list.smoothScrollToPosition(imagesAdapter.itemCount)
}
}
override fun onError(exception : ImageCaptureException)
{
exception.printStackTrace()
}
})
binding.cameraPreview.postDelayed({
binding.backgroundEffect.isVisible = true
binding.cameraPreview.postDelayed({
binding.backgroundEffect.isVisible = false
}, AppUtils.VERY_FAST_ANIMATION_MILLIS)
}, AppUtils.FAST_ANIMATION_MILLIS)
}
}
How can I improve the quality of my images? Is there any thing I should do? is there a special filter or algorithm?
i need your help please
if you took photo on Pixel probably using default cam app (GCam) - this app is fulfilled with quaility improvements backed up by some AI. tough task to comptetite with the biggest in quality... try to take a photo with some 3rd party like OpenCamera and compare this picture with one got by your app
You can use CameraX Extension feature to enable HDR & Low light.
this improves the image quality significantly.

Jetpack Compose - Wrong dimension of device

When I write this code:
val widthScreenDp = LocalConfiguration.current.screenWidthDp
val heightScreenDp = LocalConfiguration.current.screenHeightDp
val widthScreenPx = with(LocalDensity.current) { widthScreenDp.dp.toPx() }
val heightScreenPx = with(LocalDensity.current) { heightScreenDp.dp.toPx() }
For my Xiaomi mi10, the result is 1078 by 2117.5 instead of 1080 by 2340
For the Pixel5 emulator 1078 by 2062...
What is the problem ? How to have complete dimension of the device on Jetpack Compose ?
it looks like it lacks the status/nav bars heights.
Thank you !
This is the official explain for screenHeightDp:
The current height of the available screen space, in dp units,
corresponding to screen height resource qualifier.
Maybe status/nav bars is not available spaces.
You can get screen height like that:
BoxWithConstraints {
val screenHeight = maxHeight
}

ExoPlayer resize works only first time its called

As mentioned above I'm having a problem with changing the resize mode on my ExoPlayer.
I'm playing a list of videos that change on scroll and depending on the screen orientation I'm trying to make the video zoom in if its close to the screen AspectRatio, the block of code that handles that is:
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio)
val displayMetrics = DisplayMetrics()
(context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics)
val displayAspectRatio = displayMetrics.widthPixels.toFloat() / displayMetrics.heightPixels
val videoAspectRatio = width.toFloat() / height
if (abs(videoAspectRatio - displayAspectRatio) < 0.2) {
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
selectedPlayer?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
} else {
exoPlayerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
selectedPlayer?.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
}
}
But the exoPlayerView.resizeMode is visible only the first time this is called.
Is there some other way to do this? Or maybe force the view to re-render?
Thanks in advance!
You can access size of your video with : exoplayer.videoFormat.width and exoplayer.videoFormat.height, so that whenever your video is starting you start your function to calculate the ratio and do your stuff

CameraX chooses low resolution for given aspect ratio

I want to get the best quality for square aspect ratio and setup the next preview and capture configs for CameraX.
val SQUARE_ASPECT_RATIO = Rational(1, 1)
val previewConfig = PreviewConfig.Builder().apply {
setTargetAspectRatio(SQUARE_ASPECT_RATIO)
setTargetRotation(viewFinder.display.rotation)
}.build()
preview = Preview(previewConfig)
val imageCaptureConfig = ImageCaptureConfig.Builder().apply {
setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
setTargetAspectRatio(SQUARE_ASPECT_RATIO)
setTargetRotation(viewFinder.display.rotation)
}.build()
imageCapture = ImageCapture(imageCaptureConfig)
CameraX.bindToLifecycle(this, preview, imageCapture)
CameraX chooses 352x288px resolution, so result image resolution equals 288x288px.
From documentation it should be the highest resolution available for current device, but it is not.
Tested on emulator and Google Pixel 3.

Is it possible to process data of camerax preview before displaying it?

I would like to process the image that is displayed in preview and display display the processed version.
I've tried modifying it using the imageProxy in analyzer, but that does not seem to be doing anything.
I know that older camera apis are able to do it, but CameraX does not seem to have the apis.
No, this flow is not supported by cameraX. Actually, this use case has never
been supported by any Android camera API. What you have to do, is create your own renderer (preferably, OpenGL), hide the native preview surface, and send the modified frames to your renderer.
Hope this will help:
val mediaImage = imageProxy.image ?: return
var bitmap = ImageUtils.convertYuv420888ImageToBitmap(mediaImage)
val rotationDegrees = imageProxy.imageInfo.rotationDegrees
val matrix = Matrix()
matrix.postRotate(rotationDegrees.toFloat())
bitmap =
Bitmap.createBitmap(bitmap, 0, 0, mediaImage.width, mediaImage.height, matrix, true)
val cropHeight = if (bitmap.width < previewView.width) {
// if preview area larger than analysing image
val koeff = bitmap.width.toFloat() / previewView.width.toFloat()
previewView.height.toFloat() * koeff
} else {
// if preview area smaller than analysing image
val prc = 100 - (previewView.width.toFloat() / (bitmap.width.toFloat() / 100f))
previewView.height + ((previewView.height.toFloat() / 100f) * prc)
}
val cropTop = (bitmap.height / 2) - ((cropHeight) / 2)
if (cropTop > 0) {
Bitmap.createBitmap(bitmap, 0, cropTop.toInt(), bitmap.width, cropHeight.toInt())
.also { process(it, imageProxy) }
} else {
imageProxy.image?.let { process(it, imageProxy) }
}

Categories

Resources