I'm trying to make a custom camera app using hardware.camera.
I've implemented a PictureCallback which will write into a file with a certain path when the picture is taken. The data written into the file is the ByteArray returned by takePicture in camera API.
So after writing into the file, I've noticed the picture taken vertically is saved horizontally. The problem wasn't because of the Exif tag cause the byteArray had ORIENTATION_NORMAL both before and after writing into the file.
The data written into the file is the ByteArray returned by takePicture in camera API.
Here's what takePicture looks like in Camera.Java :
public final void takePicture(ShutterCallback shutter, PictureCallback raw,
PictureCallback jpeg) {
takePicture(shutter, raw, null, jpeg);
}
Here's part of the CameraPreview which will capture the photo :
Code for Camera Preview
val imageProcessor = ImageProcessor()
private val fileSaver = FileSaver(context)
fun capture() {
val callback = PictureCallback { data, _ ->
imageProcessor.process(data)?.apply {
val file = fileSaver.saveBitmap(this, outputFileName ?: DEFAULT_FILE_NAME)
onCaptureTaken?.invoke(file)
}
}
camera?.takePicture(null, null, callback)
}
Code for ImageProcessor.kt
class ImageProcessor {
fun process(data: ByteArray): Bitmap? {
val options = BitmapFactory.Options().apply {
inMutable = true
}
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, options)
return fixImageRotation(data, bitmap)
}
private fun fixImageRotation(picture: ByteArray, bitmap: Bitmap): Bitmap? {
return when (exifPostProcessor(picture)) {
ExifInterface.ORIENTATION_ROTATE_90 ->
rotateImage(bitmap, 90F)
ExifInterface.ORIENTATION_ROTATE_180 ->
rotateImage(bitmap, 180F)
ExifInterface.ORIENTATION_ROTATE_270 ->
rotateImage(
bitmap, 270F
)
ExifInterface.ORIENTATION_NORMAL -> bitmap
else -> bitmap
}
}
private fun rotateImage(source: Bitmap, angle: Float): Bitmap? {
val matrix = Matrix()
matrix.postRotate(angle)
return Bitmap.createBitmap(
source, 0, 0, source.width, source.height,
matrix, true
)
}
private fun exifPostProcessor(picture: ByteArray?): Int {
try {
return getExifOrientation(ByteArrayInputStream(picture))
} catch (e: IOException) {
e.printStackTrace()
}
return -1
}
@Throws(IOException::class)
private fun getExifOrientation(inputStream: InputStream): Int {
val exif = ExifInterface(inputStream)
return exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL
)
}
}
Code for FileSaver.kt
internal class FileSaver(context: Context) {
private val context: Context = context.applicationContext
fun saveBitmap(bitmap: Bitmap, fileName: String): File {
val file = File(mkdirsCacheFolder(), fileName)
try {
FileOutputStream(file).use { out ->
bitmap.compress(Bitmap.CompressFormat.JPEG, ORIGINAL_QUALITY, out)
}
bitmap.recycle()
} catch (e: IOException) {
e.printStackTrace()
}
return file
}
private fun mkdirsCacheFolder(): File {
return File(context.externalCacheDir, CACHE_DIRECTORY).apply {
if (!exists()) {
mkdirs()
}
}
}
companion object {
private const val ORIGINAL_QUALITY = 100
private const val CACHE_DIRECTORY = "/Lens"
}
}
Any suggestions?
EDIT:
I printed the Exif tag and it turns out to be ORIENTATION_NORMAL so I don't really know if it is rotated at all.
Edit 2 : Sample pictures were taken in portrait mode and opened from file manager[! Not that, these results are tested on both emulator and real android phone and they are the same. Preview: Preview
Captured image from file manager: Captured image from file manager
Few issues with this situation got overlapped in this question, therefore it took me so long to understand what really was going on.
What you did, you received a valid Jpeg ByteArray from the camera, and this stream contained some EXIF information, but it was missing the orientation tag. This happens on many devices, also on Xiaomi Mi.
So, you could not rotate the bitmap correctly. But you know exactly the orientation of your Activity: preview.display.rotation. This should tell you how the bitmap should be rotated in this case, but if your activity is locked into portrait, you don't even need to check. Display rotation may be in range 0…3 and these represent Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_180, or Surface.ROTATION_270.
To choose the correct rotation, you must know the way the hardware is assembled, i.e. how the camera sensor is aligned with the device. This orientation of the camera can be 0, 90, 180, or 270.
You might have seen this piece of code in different sources:
var degrees = 0
when (preview.display.rotation) {
Surface.ROTATION_0 -> degrees = 0
Surface.ROTATION_90 -> degrees = 90
Surface.ROTATION_180 -> degrees = 180
Surface.ROTATION_270 -> degrees = 270
}
val ci = Camera.CameraInfo()
Camera.getCameraInfo(cameraId, ci)
if (ci.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
degrees += ci.orientation
degrees %= 360
degrees = 360 - degrees
} else {
degrees = 360 - degrees
degrees += ci.orientation
}
camera!!.setDisplayOrientation(degrees % 360)
This code allows the camera preview to be correctly aligned with your screen; you probably have this somewhere in your app, too. Same code can be used to choose the correct bitmap rotation in your fixImageRotation() if getExifOrientation() returns ExifInterface.ORIENTATION_UNKNOWN.
In some cases, you need more detailed info about the device orientation, as explained here.
Anyways, I would recommend you to switch to the modern CameraX API, which provides better support for most devices. It allows me to call ImageCapture.setTargetRotation() and the resulting Jpeg is is rotated for me by the library.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With