Skip to content

Commit

Permalink
Fix android image rotation
Browse files Browse the repository at this point in the history
Use `ImageProxy.imageInfo.rotationDegrees` to apply the appropriate
rotation to the image in order to align the orientation of the image
with the target orientation.

Potential alternative to
mrousavy#833
  • Loading branch information
ethul committed Apr 1, 2022
1 parent fe01295 commit 404f231
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,28 +79,26 @@ suspend fun CameraView.takePhoto(options: ReadableMap): WritableMap = coroutineS
File.createTempFile("mrousavy", ".jpg", context.cacheDir).apply { deleteOnExit() }
}
)

val photo = results.first { it is ImageProxy } as ImageProxy
Log.d(CameraView.TAG, "TakePhoto photo rotationDegrees = ${photo.imageInfo.rotationDegrees}")

val file = results.first { it is File } as File

val map = Arguments.createMap()
val exif: ExifInterface?
@Suppress("BlockingMethodInNonBlockingContext")
withContext(Dispatchers.IO) {
Log.d(CameraView.TAG, "Saving picture to ${file.absolutePath}...")
val milliseconds = measureTimeMillis {
val flipHorizontally = lensFacing == CameraCharacteristics.LENS_FACING_FRONT
photo.save(file, flipHorizontally)
photo.save(file, flipHorizontally, map)
}
Log.i(CameraView.TAG_PERF, "Finished image saving in ${milliseconds}ms")
// TODO: Read Exif from existing in-memory photo buffer instead of file?
exif = if (skipMetadata) null else ExifInterface(file)
}

val map = Arguments.createMap()
map.putString("path", file.absolutePath)
map.putInt("width", photo.width)
map.putInt("height", photo.height)
map.putBoolean("isRawPhoto", photo.isRaw)

val metadata = exif?.buildMetadataMap()
map.putMap("metadata", metadata)

Expand Down
5 changes: 5 additions & 0 deletions android/src/main/java/com/mrousavy/camera/CameraView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer

@SuppressLint("RestrictedApi")
private fun updateOrientation() {
Log.d(TAG, "UpdateOrientation inputRotation = ${inputRotation}, outputRotation = ${outputRotation}")
preview?.targetRotation = inputRotation
imageCapture?.targetRotation = outputRotation
videoCapture?.targetRotation = outputRotation
Expand Down Expand Up @@ -383,9 +384,13 @@ class CameraView(context: Context, private val frameProcessorThread: ExecutorSer
}
}

Log.d(TAG, "Preview.targetRotation = ${inputRotation}")

val previewBuilder = Preview.Builder()
.setTargetRotation(inputRotation)

Log.d(TAG, "ImageCapture.targetRotation = ${outputRotation}")

val imageCaptureBuilder = ImageCapture.Builder()
.setTargetRotation(outputRotation)
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.graphics.Matrix
import android.util.Log
import androidx.camera.core.ImageProxy
import androidx.exifinterface.media.ExifInterface
import com.facebook.react.bridge.WritableMap
import com.mrousavy.camera.CameraView
import com.mrousavy.camera.InvalidFormatError
import java.io.ByteArrayOutputStream
Expand Down Expand Up @@ -48,6 +49,8 @@ fun flipImage(imageBytes: ByteArray): ByteArray {
val exif = ExifInterface(imageBytes.inputStream())
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)

Log.d(CameraView.TAG, "flipImage orientation = ${orientation}")

when (orientation) {
ExifInterface.ORIENTATION_ROTATE_180 -> {
matrix.setRotate(180f)
Expand Down Expand Up @@ -78,7 +81,25 @@ fun flipImage(imageBytes: ByteArray): ByteArray {
return stream.toByteArray()
}

fun ImageProxy.save(file: File, flipHorizontally: Boolean) {
fun rotateImage(imageBytes: ByteArray, clockwiseRotationDegrees: Int, map: WritableMap): ByteArray {
val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val matrix = Matrix()

Log.d(CameraView.TAG, "rotateImage clockwise rotation degrees = ${clockwiseRotationDegrees}")

matrix.setRotate(clockwiseRotationDegrees.toFloat())

val newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
val stream = ByteArrayOutputStream()
newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)

map.putInt("width", newBitmap.width)
map.putInt("height", newBitmap.height)

return stream.toByteArray()
}

fun ImageProxy.save(file: File, flipHorizontally: Boolean, map: WritableMap) {
when (format) {
// TODO: ImageFormat.RAW_SENSOR
// TODO: ImageFormat.DEPTH_JPEG
Expand All @@ -89,6 +110,10 @@ fun ImageProxy.save(file: File, flipHorizontally: Boolean) {
// copy image from buffer to byte array
buffer.get(bytes)

// rotate image by indicated rotation degrees in order to achive
// the target rotation set during capture
bytes = rotateImage(bytes, imageInfo.rotationDegrees, map)

if (flipHorizontally) {
val milliseconds = measureTimeMillis {
bytes = flipImage(bytes)
Expand All @@ -99,6 +124,9 @@ fun ImageProxy.save(file: File, flipHorizontally: Boolean) {
val output = FileOutputStream(file)
output.write(bytes)
output.close()

map.putString("path", file.absolutePath)
map.putBoolean("isRawPhoto", isRaw)
}
ImageFormat.YUV_420_888 -> {
// "prebuffer" simply contains the meta information about the following planes.
Expand All @@ -121,6 +149,11 @@ fun ImageProxy.save(file: File, flipHorizontally: Boolean) {
output.write(bytes) // write the byte array to file
}
output.close()

map.putString("path", file.absolutePath)
map.putInt("width", width)
map.putInt("height", height)
map.putBoolean("isRawPhoto", isRaw)
}
else -> throw InvalidFormatError(format)
}
Expand Down

0 comments on commit 404f231

Please sign in to comment.