Camera.takePicture returns 一个旋转的字节数组

Camera.takePicture returns a rotated byteArray

我正在尝试使用 hardware.camera 制作自定义相机应用。

我实现了一个PictureCallback,它会在拍摄照片时写入具有特定路径的文件。写入文件的data是相机API.takePicture返回的ByteArray

所以写入文件后,我注意到垂直拍摄的照片是水平保存的。问题不是因为 Exif 标记导致 byteArray 在写入文件之前和之后都有 ORIENTATION_NORMAL

写入文件的data是相机API.takePicture返回的ByteArray

这是 takePictureCamera.Java 中的样子:

    public final void takePicture(ShutterCallback shutter, PictureCallback raw,
            PictureCallback jpeg) {
        takePicture(shutter, raw, null, jpeg);
    }

这是 CameraPreview 的一部分,将用于拍摄照片:

相机预览代码

    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)
    }

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
        )
    }
}

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"
    }
}

有什么建议吗?

编辑: 我打印了 Exif 标签,结果是 ORIENTATION_NORMAL 所以我真的不知道它是否旋转了。

编辑 2 : 示例照片是在纵向模式下拍摄的,并从文件管理器 [! 并非如此,这些结果在模拟器和真实 android phone 上都进行了测试,它们是相同的。 预习: Preview

从文件管理器中捕获的图像: Captured image from file manager

这个问题中几乎没有与这种情况重叠的问题,因此我花了很长时间才明白到底发生了什么。

你做了什么,你从相机收到了一个有效的 Jpeg ByteArray,并且这个流包含一些 EXIF 信息,但它缺少方向标签。这发生在许多设备上,也在 Xiaomi Mi.

所以,您无法正确旋转位图。但是您确切地知道 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_0Surface.ROTATION_90Surface.ROTATION_180Surface.ROTATION_270

要选择正确的旋转方式,您必须了解硬件的组装方式,即相机传感器如何与设备对齐。 orientation of the camera 可以是 0、90、180 或 270。

您可能在不同来源看到过这段代码:

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)

此代码允许相机预览与您的屏幕正确对齐;您的应用程序中可能也有这个。相同的代码可用于在 fixImageRotation() if getExifOrientation() returns ExifInterface.ORIENTATION_UNKNOWN.

中选择正确的位图旋转

在某些情况下,您需要有关设备方向的更多详细信息,如 here 所述。

无论如何,我建议您切换到现代 CameraX API, which provides better support for most devices. It allows me to call ImageCapture.setTargetRotation(),生成的 Jpeg 由图书馆为我旋转。