Este tópico mostra como configurar casos de uso do CameraX dentro do seu app para conseguir
imagens com as informações de rotação corretas, sejam do
caso de uso ImageAnalysis
ou ImageCapture
. Portanto:
- O
Analyzer
do caso de usoImageAnalysis
precisa receber frames com a rotação correta. - O caso de uso
ImageCapture
precisa tirar fotos com a rotação correta.
Terminologia
Neste tópico, usamos a terminologia a seguir, então é importante entender o que cada termo significa:
- Orientação da tela
- Refere-se a qual lado do dispositivo está na posição para cima e pode ser um dos quatro valores: retrato, paisagem, retrato invertido ou paisagem invertida.
- Rotação da tela
- É o valor retornado por
Display.getRotation()
e representa os graus em que o dispositivo é girado no sentido anti-horário começando pela orientação natural. - Rotação desejada
- Representa o número de graus que o dispositivo precisa ser girado no sentido horário para chegar à orientação natural.
Como determinar a rotação desejada
Os exemplos a seguir mostram como determinar a rotação desejada para um dispositivo com base na orientação natural dele.
Exemplo 1: orientação natural de retrato
Dispositivo de exemplo: Pixel 3 XL | |
---|---|
Orientação natural = retrato Rotação da tela = 0 |
|
Orientação natural = retrato Rotação da tela = 90 |
Exemplo 2: orientação natural de paisagem
Dispositivo de exemplo: Pixel C | |
---|---|
Orientação natural = paisagem Rotação da tela = 0 |
|
Orientação natural = paisagem Rotação da tela = 270 |
Rotação da imagem
Qual lado está para cima? A orientação do sensor é definida no Android como um valor constante, que representa os graus (0, 90, 180, 270) em que o sensor é girado começando pela parte superior do dispositivo quando este está em uma posição natural. Em todos os casos nos diagramas, a rotação da imagem descreve como os dados precisam ser girados no sentido horário para que apareçam na posição vertical.
Os exemplos a seguir mostram qual deve ser a rotação da imagem, dependendo da orientação do sensor da câmera. Eles também pressupõem que a rotação desejada está definida como a rotação da tela.
Exemplo 1: o sensor gira 90 graus
Dispositivo de exemplo: Pixel 3 XL | |
---|---|
Rotação da tela = 0 |
|
Rotação da tela = 90 |
Exemplo 2: o sensor gira 270 graus
Dispositivo de exemplo: Nexus 5X | |
---|---|
Rotação da tela = 0 |
|
Rotação da tela = 90 |
Exemplo 3: o sensor gira 0 grau
Dispositivo de exemplo: Pixel C (Tablet) | |
---|---|
Rotação da tela = 0 |
|
Rotação da tela = 270 |
Como calcular a rotação de uma imagem
ImageAnalysis
O Analyzer
de ImageAnalysis
recebe imagens da câmera na forma de
ImageProxy
s. Cada imagem contém informações de rotação que podem ser acessadas
usando:
val rotation = imageProxy.imageInfo.rotationDegrees
Esse valor representa os graus em que a imagem precisa ser girada
em sentido horário para corresponder à rotação desejada de ImageAnalysis
. No contexto de um
app Android, a rotação desejada de ImageAnalysis
normalmente corresponde à
orientação da tela.
ImageCapture
Um callback é anexado a uma instância de ImageCapture
para sinalizar quando um resultado
de captura está pronto. O resultado pode ser a imagem capturada ou um erro.
Quando uma foto é tirada, o callback pode ser de um dos seguintes tipos:
OnImageCapturedCallback
: recebe uma imagem com acesso na memória na forma de umImageProxy
.OnImageSavedCallback
: invocado quando a imagem capturada é armazenada no local especificado porImageCapture.OutputFileOptions
. As opções podem especificar umFile
, umOutputStream
ou um local emMediaStore
.
A rotação da imagem capturada, independentemente do formato dela (ImageProxy
,
File
, OutputStream
, MediaStore Uri
), representa os graus de rotação em
que a imagem capturada precisa ser girada no sentido horário para corresponder à rotação
desejada de ImageCapture
, o que também, no contexto de um app Android, normalmente
corresponderia à orientação da tela.
É possível recuperar a rotação da imagem capturada de uma das seguintes maneiras:
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
Verificar a rotação de uma imagem
Os casos de uso ImageAnalysis
e ImageCapture
recebem ImageProxy
s da câmera
após uma solicitação de captura bem-sucedida. Um ImageProxy
encapsula uma imagem e
informações sobre ela, incluindo as de rotação. Essas informações de rotação
representam os graus em que a imagem precisa ser girada para corresponder à rotação
desejada do caso de uso.
Diretrizes de rotação desejada de ImageCapture/ImageAnalysis
Como muitos dispositivos não giram até as orientações de retrato invertido ou paisagem invertida por padrão, alguns apps Android não têm suporte a elas. Um app oferecer ou não essa compatibilidade muda a maneira como a rotação desejada dos casos de uso pode ser atualizada.
Veja abaixo duas tabelas que definem como manter a rotação desejada dos casos de uso sincronizada com a rotação da tela. A primeira mostra como fazer isso e, ao mesmo tempo, ser compatível com todas as quatro orientações. A segunda lida apenas com as orientações em que o dispositivo é girado por padrão.
Para escolher as diretrizes a serem seguidas no app:
Verifique se a
Activity
da câmera do seu app tem uma orientação bloqueada, uma orientação desbloqueada ou se ela substitui as mudanças das configurações de orientação.Decida se a
Activity
da câmera do seu app precisa processar todas as quatro orientações do dispositivo (retrato, retrato invertido, paisagem e paisagem invertida) ou se ela só vai processar as orientações às quais o dispositivo tem suporte por padrão.
Compatibilidade com as quatro orientações
Esta tabela menciona determinadas diretrizes que precisam ser seguidas nos casos em que o dispositivo não gira para o modo retrato invertido. O mesmo pode ser aplicado a dispositivos que não giram para o modo paisagem invertida.
Cenário | Diretrizes | Modo de janela única | Modo de tela dividida em várias janelas |
---|---|---|---|
Orientação desbloqueada |
Configure os casos de uso sempre
que a Activity for criada, como no
callback onCreate() da Activity .
|
||
Use onOrientationChanged()
de OrientationEventListener .
Dentro do callback, atualize a rotação desejada dos casos de uso. Isso processa casos em que o sistema não
recria a Activity mesmo depois de uma mudança de orientação, como
quando o dispositivo é girado em 180 graus.
|
Também processa casos em que a tela está em uma orientação de retrato invertido e o dispositivo não é girado para retrato invertido por padrão. |
Também processa casos em que a Activity não é
recriada quando o dispositivo é girado (por exemplo, em 90 graus). Isso acontece em
dispositivos com formato pequeno, quando o app ocupa metade da tela, e em dispositivos
maiores, quando o app ocupa dois terços da tela.
|
|
Opcional: defina a propriedade screenOrientation de Activity
como fullSensor no arquivo
AndroidManifest .
|
Isso permite que a IU fique na posição vertical quando o dispositivo está no modo retrato
invertido e que a Activity seja recriada pelo
sistema sempre que o dispositivo é girado em 90 graus.
|
Não afeta dispositivos que não giram para o modo retrato invertido por padrão. O modo de várias janelas não está disponível enquanto a tela está em uma orientação de retrato invertida. | |
Orientação bloqueada |
Configure os casos de uso apenas uma vez, quando a
Activity for criada, como no callback onCreate() da Activity .
|
||
Use onOrientationChanged()
de OrientationEventListener .
Dentro do callback, atualize a rotação que quiser dos casos de uso, exceto a visualização.
|
Também processa casos em que a Activity não é
recriada quando o dispositivo é girado (por exemplo, em 90 graus). Isso acontece em
dispositivos com formato pequeno, quando o app ocupa metade da tela, e em dispositivos
maiores, quando o app ocupa dois terços da tela.
|
||
Substituição de configChanges da orientação |
Configure os casos de uso apenas uma vez, quando a
Activity for criada, como no callback onCreate() da Activity .
|
||
Use onOrientationChanged()
de OrientationEventListener .
Dentro do callback, atualize a rotação desejada dos casos de uso.
|
Também processa casos em que a Activity não é
recriada quando o dispositivo é girado (por exemplo, em 90 graus). Isso acontece em
dispositivos com formato pequeno, quando o app ocupa metade da tela, e em dispositivos
maiores, quando o app ocupa dois terços da tela.
|
||
Opcional: defina a propriedade screenOrientation da Activity como fullSensor no arquivo AndroidManifest. | Permite que a IU fique na posição vertical quando o dispositivo está no modo retrato invertido. | Não afeta dispositivos que não giram para o modo retrato invertido por padrão. O modo de várias janelas não é compatível enquanto a tela está em uma orientação de retrato invertido. |
Compatibilidade apenas com orientações compatíveis com o dispositivo
Suporte apenas às orientações disponíveis no dispositivo por padrão, que podem ou não incluir a orientação de retrato invertido/paisagem invertida.
Cenário | Diretrizes | Modo de tela dividida em várias janelas |
---|---|---|
Orientação desbloqueada |
Configure os casos de uso sempre
que a Activity for criada, como no
callback onCreate() da Activity .
|
|
Use onDisplayChanged()
de DisplayListener . Dentro do
callback, atualize a rotação desejada dos casos de uso, como quando o
dispositivo é girado em 180 graus.
|
Também processa casos em que a Activity não é
recriada quando o dispositivo é girado (por exemplo, em 90 graus). Isso acontece em
dispositivos com formato pequeno, quando o app ocupa metade da tela, e em dispositivos
maiores, quando o app ocupa dois terços da tela.
|
|
Orientação bloqueada |
Configure os casos de uso apenas uma vez, quando a
Activity for criada, como no callback onCreate() da Activity .
|
|
Use onOrientationChanged()
de OrientationEventListener .
Dentro do callback, atualize a rotação desejada dos casos de uso.
|
Também processa casos em que a Activity não é
recriada quando o dispositivo é girado (por exemplo, em 90 graus). Isso acontece em
dispositivos com formato pequeno, quando o app ocupa metade da tela, e em dispositivos
maiores, quando o app ocupa dois terços da tela.
|
|
Substituição de configChanges da orientação |
Configure os casos de uso apenas uma vez, quando a
Activity for criada, como no callback onCreate() da Activity .
|
|
Use onDisplayChanged()
de DisplayListener . Dentro do
callback, atualize a rotação desejada dos casos de uso, como quando o
dispositivo é girado em 180 graus.
|
Também processa casos em que a Activity não é
recriada quando o dispositivo é girado (por exemplo, em 90 graus). Isso acontece em
dispositivos com formato pequeno, quando o app ocupa metade da tela, e em dispositivos
maiores, quando o app ocupa dois terços da tela.
|
Orientação desbloqueada
Uma Activity
tem orientação desbloqueada quando a orientação da tela
(como retrato ou paisagem) é igual à orientação física do dispositivo, com
exceção de retrato invertido/paisagem invertida, para as quais nem todo dispositivo oferece suporte
por padrão. Para forçar o dispositivo a girar para as quatro orientações, defina a
propriedade screenOrientation
da Activity
como fullSensor
.
No modo de várias janelas, um dispositivo que não tem suporte às orientações de retrato invertido/paisagem invertida
por padrão não vai ser girado para essas orientações, nem mesmo quando a
propriedade screenOrientation
estiver definida como 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" />
Orientação bloqueada
Uma tela tem orientação bloqueada quando permanece na mesma orientação
(como retrato ou paisagem), independentemente da orientação física do
dispositivo. Isso pode ser feito especificando a propriedade screenOrientation
da
Activity
na declaração no arquivo AndroidManifest.xml
.
Quando a tela tem uma orientação bloqueada, o sistema não destrói nem
recria a Activity
conforme o dispositivo é girado.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
Substituição das mudanças de configuração de orientação
Quando uma Activity
substitui as mudanças de configuração de orientação, o sistema
não a destrói nem a recria quando a orientação física do dispositivo muda.
No entanto, o sistema atualiza a IU para corresponder à orientação física do 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" />
Configuração dos casos de uso da câmera
Nos cenários descritos acima, é possível configurar os casos de uso da câmera quando a
Activity
é criada pela primeira vez.
No caso de uma Activity
com orientação desbloqueada, essa configuração é feita
sempre que o dispositivo é girado, conforme o sistema destrói e recria a
Activity
nas mudanças de orientação. Isso faz com que os casos de uso definam
a rotação desejada para sempre corresponder à orientação da tela por padrão.
No caso de uma Activity
com orientação bloqueada ou que substitua
as mudanças de configuração de orientação, essa configuração é feita uma vez, quando a Activity
é criada.
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) } }
Configuração do OrientationEventListener
O uso de um OrientationEventListener
permite atualizar de maneira contínua a rotação
desejada dos casos de uso da câmera conforme as mudanças de orientação do 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() } }
Configuração do DisplayListener
O uso de um DisplayListener
permite atualizar a rotação desejada dos casos de uso
da câmera em determinadas situações, por exemplo, quando o sistema não destrói
nem recria a Activity
depois que o dispositivo é girado em 180 graus.
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) } }