Reputation: 1847
I have followed Google CameraX code lab to implement custom camera. Camera preview is fine but when i take image after image capture image is rotated. I am taking image in portrait mode but saved image is in landscape. Here is the method to configure camera
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.setTargetRotation(this.windowManager.defaultDisplay.rotation)
.build()
.also {
it.setSurfaceProvider(viewFinder.createSurfaceProvider())
}
imageCapture = ImageCapture.Builder()
.setTargetRotation(this.windowManager.defaultDisplay.rotation)
.build()
val imageAnalyzer = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
Log.d(TAG, "Average luminosity: $luma")
})
}
// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()
// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture, imageAnalyzer)
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
Here is the method to capture image:
private fun takePhoto() {
val imageCapture = imageCapture ?: return
// Create time-stamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")
// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, savedUri)
ivCapturedImage.setImageBitmap(bitmap)
setCaptureUI(false)
Log.d(TAG, msg)
}
})
}
Do i need to rotate the image by myself after it is taken using EXIF or i can fix it while configuring camera?
Upvotes: 18
Views: 17300
Reputation: 434
The simplest solution that works for me.
Get the rotationDegrees from imageProxy and rotate your bitmap by that degree.
Matrix matrix = new Matrix();
matrix.postRotate((float)imageProxy.getImageInfo().getRotationDegrees());
Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
binding.imgPreview.setImageBitmap(bitmap2);
Upvotes: 2
Reputation: 1381
This simple code worked for me:
Java Version:
Context context = ... //The current Context
Camera camera = cameraProvider.bindToLifecycle(...); //The one you get after initializing the camera
ImageProxy image = ... //The one that takePicture or Analyze give you
int currentLensOrientation = ... //CameraSelector.LENS_FACING_BACK or CameraSelector.LENS_FACING_FRONT
int rotationDirection = currentLensOrientation == CameraSelector.LENS_FACING_BACK ? 1 : -1;
int constantRotation = image.getImageInfo().getRotationDegrees() - camera.getCameraInfo().getSensorRotationDegrees();
int rotationDegrees = camera.getCameraInfo().getSensorRotationDegrees() - context.getDisplay().getRotation() * 90 + constantRotation * rotationDirection;
Kotlin Version:
val context: Context = ... //The current Context
val camera: Camera? = cameraProvider.bindToLifecycle(...) //The one you get after initializing the camera
val image: ImageProxy = ... //The one that takePicture or Analyze give you
val currentLensOrientation: Int = ... //CameraSelector.LENS_FACING_BACK or CameraSelector.LENS_FACING_FRONT
val rotationDirection = if (currentLensOrientation == CameraSelector.LENS_FACING_BACK) 1 else -1
val constantRotation = image.imageInfo.rotationDegrees - camera!!.cameraInfo.sensorRotationDegrees
val rotationDegrees = camera!!.cameraInfo.sensorRotationDegrees - context.display!!.rotation * 90 + constantRotation * rotationDirection
Then I used the rotationDegrees
to rotate the ImageProxy that CameraX passes you in the takePicture and analyze's callbacks.
Here you can find the full Java code if you need it: https://github.com/CristianDavideConte/SistemiDigitali/blob/7b40e50d8b2fbdf4e4a61edba7443da92b96c58d/app/src/main/java/com/example/sistemidigitali/views/CameraProviderView.java#L207
Upvotes: 1
Reputation: 2546
I've been having the same problem; as far as I can understand, from replies on tickets such as this or this, the team behind CameraX does not like to meddle with raw image data as returned from the hardware, and would very much like to just limit themselves to setting EXIF metadata.
So I just worked around this, and starting from a code quite similar to yours (well, heavily inspired from the one in the codelab), I added this:
Display d = getDisplay();
if (d != null) {
iCapture.setTargetRotation(d.getRotation());
}
just before calling iCapture.takePicture()
(iCapture being my ImageCapture
use-case instance). This ensures that the target rotation in the EXIF file metadata will be consistent with actual display rotation when the photo is taken.
Then, after data is received (in my case, on the onImageSaved()
handler), I check the EXIF metadata for rotation and, in that case, manually rotate the image for the required degrees and save a different file to ensure no EXIF tags are kept with incoherent values.
try {
ExifInterface ei = new ExifInterface(tempFile.getAbsolutePath());
if (ei.getRotationDegrees() != 0) {
actualPicture = ImageUtil.rotateDegrees(tempFile, ei.getRotationDegrees());
}
} catch (IOException exc) {
Log.e(TAG, "Tried to fix image rotation but could not continue: " + exc,getMessage());
}
where ImageUtil is a custom class of image tools, and rotateDegrees does just that, with a custom matrix initialized like this:
//inside rotateDegrees(), degrees is the parameter to the function
Matrix m = new Matrix();
m.postRotate(degrees);
and creating a new bitmap starting from the one imported from the original file:
Bitmap b = Bitmap.createBitmap(sourceFile, 0, 0, sourceFile.getWidth(), sourceFile.getHeight(), m, true);
b.compress(Bitmap.CompressFormat.JPEG, 85, /* a suitably-created output stream */);
Still, I'd like for CameraX to handle image rotation directly, without relying on metadata (that, by their own admission, very few libraries and tools go read and actually handle).
Upvotes: -1
Reputation: 11071
By default, ImageCapture set the orientation of the capture to the display rotation. If the image is saved to disk, the rotation will be in the EXIF.
Is your device in locked portrait mode? In that case, display rotation does not match the device's orientation, and you will need to set the target rotation yourself. Example.
// The value is whatever the display rotation should be, if the device orientation is not locked.
imageCapture.setTargetRotation(...)
Or, you could simply use the LifecycleCameraController API. It handles the rotation for you and make all the use cases consistent in a WYSIWYG way.
Upvotes: 9
Reputation: 1847
I have used this class to rotate image
object CaptureImageHelper {
/**
* This method is responsible for solving the rotation issue if exist. Also scale the images to
* 1024x1024 resolution
*
* @param context The current context
* @param selectedImage The Image URI
* @return Bitmap image results
* @throws IOException
*/
@Throws(IOException::class)
fun handleSamplingAndRotationBitmap(context: Context, selectedImage: Uri?): Bitmap? {
val MAX_HEIGHT = 1024
val MAX_WIDTH = 1024
// First decode with inJustDecodeBounds=true to check dimensions
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
var imageStream: InputStream = context.getContentResolver().openInputStream(selectedImage!!)!!
BitmapFactory.decodeStream(imageStream, null, options)
imageStream.close()
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT)
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false
imageStream = context.getContentResolver().openInputStream(selectedImage!!)!!
var img = BitmapFactory.decodeStream(imageStream, null, options)
img = rotateImageIfRequired(img!!, selectedImage)
return img
}
/**
* Calculate an inSampleSize for use in a [BitmapFactory.Options] object when decoding
* bitmaps using the decode* methods from [BitmapFactory]. This implementation calculates
* the closest inSampleSize that will result in the final decoded bitmap having a width and
* height equal to or larger than the requested width and height. This implementation does not
* ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
* results in a larger bitmap which isn't as useful for caching purposes.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int, reqHeight: Int
): Int {
// Raw height and width of image
val height = options.outHeight
val width = options.outWidth
var inSampleSize = 1
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
val heightRatio =
Math.round(height.toFloat() / reqHeight.toFloat())
val widthRatio =
Math.round(width.toFloat() / reqWidth.toFloat())
// Choose the smallest ratio as inSampleSize value, this will guarantee a final image
// with both dimensions larger than or equal to the requested height and width.
inSampleSize = if (heightRatio < widthRatio) heightRatio else widthRatio
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
val totalPixels = width * height.toFloat()
// Anything more than 2x the requested pixels we'll sample down further
val totalReqPixelsCap = reqWidth * reqHeight * 2.toFloat()
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++
}
}
return inSampleSize
}
/**
* Rotate an image if required.
*
* @param img The image bitmap
* @param selectedImage Image URI
* @return The resulted Bitmap after manipulation
*/
@Throws(IOException::class)
private fun rotateImageIfRequired(img: Bitmap, selectedImage: Uri): Bitmap? {
val ei = ExifInterface(selectedImage.path)
val orientation: Int =
ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
return when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> rotateImage(img, 90)
ExifInterface.ORIENTATION_ROTATE_180 -> rotateImage(img, 180)
ExifInterface.ORIENTATION_ROTATE_270 -> rotateImage(img, 270)
else -> img
}
}
private fun rotateImage(img: Bitmap, degree: Int): Bitmap? {
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
val rotatedImg =
Bitmap.createBitmap(img, 0, 0, img.width, img.height, matrix, true)
img.recycle()
return rotatedImg
}
}
Upvotes: 4
Reputation: 366
I'm suffering from same situation. I solved this with hacky way.
My solution is:
fun Bitmap.rotate(degrees: Float): Bitmap {
val matrix = Matrix().apply { postRotate(degrees) }
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
}
Usage :
imageViewCapturedImage.setImageBitmap(bitmap?.rotate(90F))
Upvotes: 3