En este tema, se muestra el proceso de configuración de casos de uso de CameraX dentro de tu app para obtener imágenes con la información de rotación correcta, ya sea desde el caso de uso del elemento ImageAnalysis
o ImageCapture
. Entonces:
- El elemento
Analyzer
del caso de uso del objetoImageAnalysis
debe recibir marcos con la rotación correcta. - El caso de uso del objeto
ImageCapture
debe tomar fotos con la rotación correcta.
Terminología
En este tema, se utiliza la siguiente terminología, por lo que es importante comprender el significado de cada término:
- Orientación de la pantalla
- Se refiere a qué lado del dispositivo está en la posición hacia arriba. Puede ser uno de cuatro valores: vertical, horizontal, horizontal inverso o vertical inverso.
- Rotación de la pantalla
- Es el valor que muestra el objeto
Display.getRotation()
y representa los grados a los que se rota el dispositivo en sentido contrario a las manecillas del reloj desde la orientación natural. - Rotación objetivo
- Representa la cantidad de grados a los que se debe rotar el dispositivo en el sentido de las manecillas del reloj para alcanzar su orientación natural.
Cómo determinar la rotación objetivo
En los siguientes ejemplos, se muestra cómo determinar la rotación objetivo de un dispositivo en función de su orientación natural.
Ejemplo 1: orientación natural vertical
Ejemplo de dispositivo: Pixel 3 XL | |
---|---|
Orientación natural = vertical Rotación de la pantalla = 0 |
|
Orientación natural = vertical Rotación de la pantalla = 90 |
Ejemplo 2: orientación natural horizontal
Ejemplo de dispositivo: Pixel C | |
---|---|
Orientación natural = horizontal Rotación de la pantalla = 0 |
|
Orientación natural = horizontal Rotación de la pantalla = 270 |
Rotación de la imagen
¿Qué extremo está orientado hacia arriba? En Android, la orientación del sensor se define como un valor constante, que representa los grados (0, 90, 180, 270) a los que se rota el sensor desde la parte superior del dispositivo cuando se encuentra en una posición natural. En todos los casos de los diagramas, la rotación de la imagen describe cómo se deben rotar los datos en el sentido de las manecillas del reloj para que se muestren en posición vertical.
En los siguientes ejemplos, se muestra cómo debería rotar la imagen según la orientación del sensor de la cámara. En estos ejemplos, también se presupone que la rotación objetivo se establece en la rotación de la pantalla.
Ejemplo 1: rotación del sensor a 90 grados
Ejemplo de dispositivo: Pixel 3 XL | |
---|---|
Rotación de la pantalla = 0 |
|
Rotación de la pantalla = 90 |
Ejemplo 2: rotación del sensor a 270 grados
Ejemplo de dispositivo: Nexus 5X | |
---|---|
Rotación de la pantalla = 0 |
|
Rotación de la pantalla = 90 |
Ejemplo 3: rotación del sensor a 0 grados
Ejemplo de dispositivo: Pixel C (tablet) | |
---|---|
Rotación de la pantalla = 0 |
|
Rotación de la pantalla = 270 |
Cómo calcular la rotación de una imagen
ImageAnalysis
El elemento Analyzer
del objeto ImageAnalysis
recibe imágenes de la cámara en forma de ImageProxy
. Cada imagen contiene información de rotación, a la que se puede acceder con el siguiente código:
val rotation = imageProxy.imageInfo.rotationDegrees
Este valor representa los grados a los que se debe rotar la imagen en el sentido de las manecillas del reloj para que coincida con la rotación objetivo del elemento ImageAnalysis
. En el contexto de una app para Android, en general, la rotación objetivo del elemento ImageAnalysis
coincidiría con la orientación de la pantalla.
ImageCapture
Se adjunta una devolución de llamada a una instancia ImageCapture
para indicar cuándo está listo un resultado de captura. El resultado puede ser la imagen capturada o un error.
Cuando tomas una foto, la devolución de llamada proporcionada puede ser de uno de los siguientes tipos:
OnImageCapturedCallback
: Recibe una imagen con acceso en la memoria, en forma de un objetoImageProxy
.OnImageSavedCallback
: Se invoca cuando la imagen capturada se almacena correctamente en la ubicación que especificóImageCapture.OutputFileOptions
. Las opciones pueden especificar unFile
, unOutputStream
o una ubicación enMediaStore
específicos.
La rotación de la imagen capturada, sin importar su formato (ImageProxy
, File
, OutputStream
, MediaStore Uri
) representa los grados de rotación a los que se debe rotar la imagen capturada en sentido horario para que coincida con la rotación objetivo del elemento ImageCapture
, que, nuevamente, en el contexto de una app para Android, coincidiría generalmente con la orientación de la pantalla.
Se puede recuperar la rotación de la imagen capturada de una de las siguientes maneras:
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
Cómo verificar la rotación de una imagen
Los casos de uso de los objetos ImageAnalysis
y ImageCapture
reciben el elementos ImageProxy
de la cámara después solicitar una captura correctamente. Un elemento ImageProxy
une una imagen y la información sobre esta, lo que incluye su rotación. Esta información de rotación representa los grados a los que se debe rotar la imagen para que coincida con la rotación objetivo del caso de uso.
Lineamientos de la rotación objetivo de ImageCapture y de ImageAnalysis
Como muchos dispositivos no rotan al modo vertical inverso ni horizontal inverso de forma predeterminada, algunas apps para Android no admiten estas orientaciones. La forma en que se puede actualizar la rotación objetivo de los casos de uso cambia en función de si una app admite o no estas orientaciones.
A continuación, se muestran dos tablas en las que se define cómo mantener la rotación objetivo de los casos de uso en sincronización con la rotación de la pantalla. En el primer ejemplo, se muestra cómo hacerlo mientras se admiten las cuatro orientaciones; en el segundo, solo se describen las orientaciones a las que rota el dispositivo de forma predeterminada.
Para elegir los lineamientos que se deben cumplir en la app, haz lo siguiente:
Verifica si el objeto
Activity
de la cámara de tu app tiene una orientación bloqueada, una orientación desbloqueada o si anula los cambios en la configuración de orientación.Decide si el objeto
Activity
de la cámara de tu app debe controlar las cuatro orientaciones del dispositivo (vertical, vertical inverso, horizontal y horizontal inverso) o si solo deben controlar las orientaciones que admite el dispositivo de forma predeterminada.
Cómo admitir las cuatro orientaciones
En esta tabla, se incluyen ciertos lineamientos que se deben cumplir en los casos en los que el dispositivo no rota al modo vertical inverso. Estos mismos lineamientos se pueden aplicar en dispositivos que no rotan al modo horizontal inverso.
Situación | Lineamientos | Modo de ventana única | Modo de pantalla dividida en multiventana |
---|---|---|---|
Orientación desbloqueada |
Configura los casos de uso cada vez que se crea el objeto Activity , como en la devolución de llamada del elemento onCreate() del parámetro Activity .
|
||
Usa el objeto onOrientationChanged() del elemento OrientationEventListener .
Dentro de la devolución de llamada, actualiza la rotación objetivo de los casos de uso. De esta manera, se controlan los casos en los que el sistema no vuelve a crear el objeto Activity incluso después cambiar la orientación, como cuando se rota el dispositivo a 180 grados.
|
También controla cuando la pantalla está en orientación vertical inversa y el dispositivo no rota al modo vertical inverso de forma predeterminada. |
También controla los casos en los que no se vuelve a crear el objeto Activity cuando se rota el dispositivo (por ejemplo, a 90 grados), lo que sucede en dispositivos pequeños de factor de forma cuando la app ocupa la mitad de la pantalla y en dispositivos más grandes cuando ocupa dos tercios.
|
|
Opcional: Configura la propiedad screenOrientation del objeto Activity en el elemento fullSensor del archivo AndroidManifest .
|
De esta manera, se permite que la IU se mantenga en posición vertical cuando el dispositivo se encuentra en modo vertical inverso y que el sistema vuelva a crear el objeto Activity cuando se rota el dispositivo a 90 grados.
|
No tiene ningún efecto en dispositivos que no rotan al modo vertical inverso de forma predeterminada. No se admite el modo multiventana mientras la pantalla se encuentra en orientación vertical inversa. | |
Orientación bloqueada |
Configura los casos de uso solo una vez, cuando se crea el objeto Activity por primera vez, como en la devolución de llamada del elemento onCreate() del parámetro Activity .
|
||
Usa el objeto onOrientationChanged() del elemento OrientationEventListener .
Dentro de la devolución de llamada, actualiza la rotación objetivo de los casos de uso, excepto Preview.
|
También controla los casos en los que no se vuelve a crear el objeto Activity cuando se rota el dispositivo (por ejemplo, a 90 grados), lo que sucede en dispositivos pequeños de factor de forma cuando la app ocupa la mitad de la pantalla y en dispositivos más grandes cuando ocupa dos tercios.
|
||
Anulación de los cambios en la configuración de orientación |
Configura los casos de uso solo una vez, cuando se crea el objeto Activity por primera vez, como en la devolución de llamada del elemento onCreate() del parámetro Activity .
|
||
Usa el objeto onOrientationChanged() del elemento OrientationEventListener .
Dentro de la devolución de llamada, actualiza la rotación objetivo de los casos de uso.
|
También controla los casos en los que no se vuelve a crear el objeto Activity cuando se rota el dispositivo (por ejemplo, a 90 grados), lo que sucede en dispositivos pequeños de factor de forma cuando la app ocupa la mitad de la pantalla y en dispositivos más grandes cuando ocupa dos tercios.
|
||
Opcional: Configura la propiedad screenOrientation del objeto Activity en el elemento fullSensor del archivo AndroidManifest. | Permite que la IU se mantenga en posición vertical cuando el dispositivo se encuentra en modo vertical inverso. | No tiene ningún efecto en dispositivos que no rotan al modo vertical inverso de forma predeterminada. No se admite el modo multiventana mientras la pantalla se encuentra en orientación vertical inversa. |
Cómo admitir solo orientaciones compatibles con el dispositivo
Admite solo las orientaciones compatibles con el dispositivo de forma predeterminada (que puede incluir el modo vertical y horizontal inverso).
Situación | Lineamientos | Modo de pantalla dividida en multiventana |
---|---|---|
Orientación desbloqueada |
Configura los casos de uso cada vez que se crea el objeto Activity , como en la devolución de llamada del elemento onCreate() del parámetro Activity .
|
|
Usa el objeto onDisplayChanged() del elemento DisplayListener . Dentro de la devolución de llamada, actualiza la rotación objetivo de los casos de uso, como cuando se rota el dispositivo a 180 grados.
|
También controla los casos en los que no se vuelve a crear el objeto Activity cuando se rota el dispositivo (por ejemplo, a 90 grados), lo que sucede en dispositivos pequeños de factor de forma cuando la app ocupa la mitad de la pantalla y en dispositivos más grandes cuando ocupa dos tercios.
|
|
Orientación bloqueada |
Configura los casos de uso solo una vez, cuando se crea el objeto Activity por primera vez, como en la devolución de llamada del elemento onCreate() del parámetro Activity .
|
|
Usa el objeto onOrientationChanged() del elemento OrientationEventListener .
Dentro de la devolución de llamada, actualiza la rotación objetivo de los casos de uso.
|
También controla los casos en los que no se vuelve a crear el objeto Activity cuando se rota el dispositivo (por ejemplo, a 90 grados), lo que sucede en dispositivos pequeños de factor de forma cuando la app ocupa la mitad de la pantalla y en dispositivos más grandes cuando ocupa dos tercios.
|
|
Anulación de los cambios en la configuración de orientación |
Configura los casos de uso solo una vez, cuando se crea el objeto Activity por primera vez, como en la devolución de llamada del elemento onCreate() del parámetro Activity .
|
|
Usa el objeto onDisplayChanged() del elemento DisplayListener . Dentro de la devolución de llamada, actualiza la rotación objetivo de los casos de uso, como cuando se rota el dispositivo a 180 grados.
|
También controla los casos en los que no se vuelve a crear el objeto Activity cuando se rota el dispositivo (por ejemplo, a 90 grados), lo que sucede en dispositivos pequeños de factor de forma cuando la app ocupa la mitad de la pantalla y en dispositivos más grandes cuando ocupa dos tercios.
|
Orientación desbloqueada
Un objeto Activity
tiene una orientación desbloqueada cuando su orientación de la pantalla (por ejemplo, horizontal o vertical) coincide con la orientación física del dispositivo, a excepción de la orientación horizontal o vertical inversa, que algunos dispositivos no admiten de forma predeterminada. Para forzar que el dispositivo rote a las cuatro orientaciones, establece la propiedad screenOrientation
del objeto Activity
en el elemento fullSensor
.
En el modo multiventana, un dispositivo que no admite la orientación horizontal o vertical inversa de forma predeterminada no rotará al modo horizontal o vertical, incluso cuando se configure su propiedad screenOrientation
en el elemento 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" />
Orientación bloqueada
Una pantalla tiene una orientación bloqueada cuando se mantiene en la misma orientación de la pantalla (por ejemplo, horizontal o vertical), sin importar la orientación física del dispositivo. Para ello, especifica la propiedad screenOrientation
del objeto Activity
dentro de su declaración en el archivo AndroidManifest.xml
.
Cuando la pantalla tiene una orientación bloqueada, el sistema no destruye ni vuelve a crear el objeto Activity
cuando se rota el dispositivo.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
Anulación de los cambios en la configuración de orientación
Cuando un objeto Activity
anula los cambios en la configuración de orientación, el sistema no lo destruye ni vuelve a crearlo cuando cambia la orientación física del dispositivo.
Sin embargo, el sistema actualiza la IU para que coincida con la orientación física del dispositivo.
<!-- 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" />
Configuración de los casos de uso de la cámara
En las situaciones que se describen anteriormente, se pueden configurar los casos de uso de la cámara cuando se crea el objeto Activity
por primera vez.
En el caso de un objeto Activity
con una orientación desbloqueada, se realiza esta configuración cada vez que se rota el dispositivo, ya que el sistema destruye y vuelve a crear el objeto Activity
con los cambios de orientación. Como resultado, los casos de uso configuran su rotación objetivo para que coincida con la orientación de la pantalla de forma predeterminada cada vez que se rote el dispositivo.
En el caso de un objeto Activity
con una orientación bloqueada o una que anule los cambios en la configuración de orientación, se realiza esta configuración una vez, cuando se crea el objeto Activity
por primera vez.
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) } }
Configuración de OrientationEventListener
Usar un objeto OrientationEventListener
te permite actualizar, de manera continua, la rotación objetivo de los casos de uso de la cámara a medida que cambia la orientación del dispositivo.
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() } }
Configuración de DisplayListener
Usar un objeto DisplayListener
te permite actualizar la rotación objetivo de los casos de uso de la cámara en situaciones particulares, por ejemplo, cuando el sistema no destruye ni vuelve a crear el objeto Activity
después de que se rota el dispositivo a 180 grados.
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) } }