Android NDK开发详解相机之CameraX 用例旋转


本主题介绍了如何在您的应用中设置 CameraX 用例才能使 ImageAnalysis 或 ImageCapture 用例中的图片具有正确的旋转信息。因此:

ImageAnalysis 用例的 Analyzer 应收到具有正确旋转信息的帧。
ImageCapture 用例应拍摄具有正确旋转信息的照片。

术语

本主题使用了以下术语,因此了解每个术语的含义非常重要:

屏幕方向
此术语指设备哪一侧朝上,可为以下四个值之一:纵向、横向、反向纵向或反向横向。
屏幕旋转角度
这是 Display.getRotation() 返回的值,表示设备从其自然屏幕方向逆时针旋转的角度值。
目标旋转角度
此术语表示顺时针旋转设备使其达到自然屏幕方向需要旋转的度数。

如何确定目标旋转角度

以下示例展示了如何根据设备的自然屏幕方向确定其目标旋转角度。

示例 1:纵向自然屏幕方向

在这里插入图片描述

示例 2:横向自然屏幕方向

在这里插入图片描述

图片旋转角度

哪边朝上?在 Android 中,传感器方向被定义为一个常量值,表示当设备处于自然位置时,相对于设备顶部,传感器旋转的角度(0、90、180、270)。对于图表中的所有情况,图片旋转角度描述的都是应如何顺时针旋转数据才能使其纵向显示。

以下示例展示了应该如何根据相机传感器方向确定图片旋转角度。这些示例还假设,目标旋转角度已设为屏幕旋转角度。

示例 1:传感器旋转 90 度

在这里插入图片描述

示例 2:传感器旋转 270 度

在这里插入图片描述

示例 3:传感器旋转 0 度

在这里插入图片描述

计算图片的旋转角度

ImageAnalysis

ImageAnalysis 的 Analyzer 以 ImageProxy 的形式从相机接收图片。每张图片都包含旋转信息,这些信息可以通过运行以下代码获取:

val rotation = imageProxy.imageInfo.rotationDegrees

此值表示需要将图片顺时针旋转多少度才能与 ImageAnalysis 的目标旋转角度保持一致。在 Android 应用中,ImageAnalysis 的目标旋转角度通常与屏幕的方向一致。

ImageCapture

ImageCapture 实例附加有一个回调,用于在拍摄结果就绪时发出信号。拍摄结果要么是拍摄的图片,要么是错误。

在拍照时,提供的回调可为以下类型之一:

OnImageCapturedCallback:以 ImageProxy 的形式接收具有内存访问权限的图片。
OnImageSavedCallback:在拍摄的图片已成功存储到 ImageCapture.OutputFileOptions 指定的位置后调用。通过这些选项可以指定 File、OutputStream 位置或 MediaStore 中的位置。

无论拍摄的图片是什么格式(ImageProxy、File、OutputStream、MediaStore Uri),图片的旋转角度都表示需要将拍摄的图片顺时针旋转多少度才能与 ImageCapture 的目标旋转角度保持一致。而在 Android 应用中,该目标旋转角度通常与屏幕的方向一致。

您可以通过以下其中一种方式检索所拍图片的旋转信息:

ImageProxy


val rotation = imageProxy.imageInfo.rotationDegrees

File

val exif = Exif.createFromFile(file)
val rotation = exif.rotation

OutputStream

val byteArray = outputStream.toByteArray()
val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray))
val rotation = exif.rotation

MediaStore uri

val inputStream = contentResolver.openInputStream(outputFileResults.savedUri)
val exif = Exif.createFromInputStream(inputStream)
val rotation = exif.rotation

验证图片的旋转信息

ImageAnalysis 和 ImageCapture 用例在拍摄请求成功后从相机接收 ImageProxy。ImageProxy 中封装了图片及其相关信息,包括其旋转信息。这些旋转信息表示必须将图片旋转多少度才能与相应用例的目标旋转角度保持一致。
图片的旋转信息验证流程
在这里插入图片描述

ImageCapture/ImageAnalysis 目标旋转角度准则

由于许多设备在默认情况下不会旋转到反向纵向或反向横向,因此某些 Android 应用不支持这两个屏幕方向。应用是否支持这两个屏幕方向会改变用例的目标旋转角度更新方式。

以下两个表定义了如何保持用例的目标旋转角度与屏幕旋转角度同步。第一个表展示如何在支持全部四个屏幕方向时保持同步;第二个表仅处理设备可默认旋转到的屏幕方向。

为了选择要在您的应用中遵循的准则,请执行以下操作:

验证应用的相机 Activity 是已锁定屏幕方向、未锁定屏幕方向,还是会覆盖屏幕方向配置更改。

确定应用的相机 Activity 是应该处理全部四个设备屏幕方向(纵向、反向纵向、横向和反向横向),还是只应该处理运行该应用的设备默认支持的屏幕方向。

支持全部四个屏幕方向

下表列出了设备不会旋转到反向纵向的情况下应遵循的一些准则。同样的准则也适用于不会旋转到反向横向的设备。
在这里插入图片描述

仅支持设备支持的屏幕方向

仅支持设备默认支持的屏幕方向(可能包括也可能不包括反向纵向/反向横向)。
在这里插入图片描述

屏幕方向未锁定

如果除了某些设备默认不支持的反向纵向/横向以外,Activity 的屏幕方向(例如纵向或横向)与设备的物理屏幕方向保持一致,这种情况就属于屏幕方向未锁定。如需强制设备向全部四个方向旋转,请将 Activity 的 screenOrientation 属性设置为 fullSensor。

在多窗口模式中,默认不支持反向纵向/横向的设备不会旋转到反向纵向/横向,即使设备的 screenOrientation 属性设置为 fullSensor 也不例外。


<!-- The Activity has an unlocked orientation, but might not rotate to reverse
portrait/landscape in single-window mode if the device doesn't support it by
default. -->
<activity android:name=".UnlockedOrientationActivity" />

<!-- The Activity has an unlocked orientation, and will rotate to all four
orientations in single-window mode. -->
<activity
   android:name=".UnlockedOrientationActivity"
   android:screenOrientation="fullSensor" />

屏幕方向已锁定

如果无论设备的物理屏幕方向如何,屏幕都保持相同的屏幕方向(例如纵向或横向),这种情况就属于屏幕方向已锁定。通过在 AndroidManifest.xml 文件的相应声明中指定 Activity 的 screenOrientation 属性可以做到这一点。

当屏幕方向已锁定时,系统不会在旋转设备时销毁并重新创建 Activity。

屏幕方向配置更改已覆盖

当 Activity 会覆盖屏幕方向配置更改时,系统不会在设备的物理屏幕方向发生变化时销毁并重新创建此 activity。但是,系统会更新界面,使其与设备的物理屏幕方向保持一致。

<!-- The Activity's UI might not rotate in reverse portrait/landscape if the
device doesn't support it by default. -->
<activity
   android:name=".OrientationConfigChangesOverriddenActivity"
   android:configChanges="orientation|screenSize" />

<!-- The Activity's UI will rotate to all 4 orientations in single-window
mode. -->
<activity
   android:name=".OrientationConfigChangesOverriddenActivity"
   android:configChanges="orientation|screenSize"
   android:screenOrientation="fullSensor" />

相机用例设置

在上述场景中,可在首次创建 Activity 时设置相机用例。

如果 Activity 的屏幕方向未锁定,那么每次旋转设备时,系统都会完成此设置,因为系统会在屏幕方向发生变化时销毁并重新创建 Activity。这样一来,用例就会每次都默认设置其目标旋转角度,以便与屏幕方向保持一致。

如果 Activity 的屏幕方向已锁定或屏幕方向配置更改会被覆盖,系统只会在首次创建 Activity 时完成一次此设置。

class CameraActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)

       val cameraProcessFuture = ProcessCameraProvider.getInstance(this)
       cameraProcessFuture.addListener(Runnable {
          val cameraProvider = cameraProcessFuture.get()

          // By default, the use cases set their target rotation to match the
          // display’s rotation.
          val preview = buildPreview()
          val imageAnalysis = buildImageAnalysis()
          val imageCapture = buildImageCapture()

          cameraProvider.bindToLifecycle(
              this, cameraSelector, preview, imageAnalysis, imageCapture)
       }, mainExecutor)
   }
}

OrientationEventListener 设置

使用 OrientationEventListener 可以让您随着设备屏幕方向的变化持续更新相机用例的目标旋转角度。

class CameraActivity : AppCompatActivity() {

    private val orientationEventListener by lazy {
        object : OrientationEventListener(this) {
            override fun onOrientationChanged(orientation: Int) {
                if (orientation == ORIENTATION_UNKNOWN) {
                    return
                }

                val rotation = when (orientation) {
                     in 45 until 135 -> Surface.ROTATION_270
                     in 135 until 225 -> Surface.ROTATION_180
                     in 225 until 315 -> Surface.ROTATION_90
                     else -> Surface.ROTATION_0
                 }

                 imageAnalysis.targetRotation = rotation
                 imageCapture.targetRotation = rotation
            }
        }
    }

    override fun onStart() {
        super.onStart()
        orientationEventListener.enable()
    }

    override fun onStop() {
        super.onStop()
        orientationEventListener.disable()
    }
}

DisplayListener 设置

使用 DisplayListener 可以让您在特定情况下更新相机用例的目标旋转角度,例如在设备旋转了 180 度后系统没有销毁并重新创建 Activity 的情况下。

class CameraActivity : AppCompatActivity() {

    private val displayListener = object : DisplayManager.DisplayListener {
        override fun onDisplayChanged(displayId: Int) {
            if (rootView.display.displayId == displayId) {
                val rotation = rootView.display.rotation
                imageAnalysis.targetRotation = rotation
                imageCapture.targetRotation = rotation
            }
        }

        override fun onDisplayAdded(displayId: Int) {
        }

        override fun onDisplayRemoved(displayId: Int) {
        }
    }

    override fun onStart() {
        super.onStart()
        val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
        displayManager.registerDisplayListener(displayListener, null)
    }

    override fun onStop() {
        super.onStop()
        val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
        displayManager.unregisterDisplayListener(displayListener)
    }
}

本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。

最后更新时间 (UTC):2023-04-05。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

五一编程

程序之路有我与你同行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值