Rotazioni del caso d'uso di CameraX

Questo argomento illustra come configurare i casi d'uso di CameraX all'interno della tua app per recuperare immagini con le informazioni sulla rotazione corrette, che si tratti del caso d'uso di ImageAnalysis o di ImageCapture. Così:

  • Il Analyzer del caso d'uso ImageAnalysis dovrebbe ricevere frame con la rotazione corretta.
  • Il caso d'uso di ImageCapture dovrebbe scattare foto con la rotazione corretta.

Terminologia

Questo argomento utilizza la seguente terminologia, pertanto è importante comprendere il significato di ogni termine:

Orientamento del display
Indica quale lato del dispositivo si trova nella posizione più alta e può avere uno dei quattro valori seguenti: verticale, orizzontale, verticale o orizzontale invertito.
Rotazione del display
Si tratta del valore restituito da Display.getRotation() e rappresenta i gradi di rotazione del dispositivo in senso antiorario rispetto al suo orientamento naturale.
Rotazione target
Rappresenta il numero di gradi attraverso i quali ruotare il dispositivo in senso orario per raggiungere il suo orientamento naturale.

Come determinare la rotazione target

I seguenti esempi mostrano come determinare la rotazione target di un dispositivo in base al suo orientamento naturale.

Esempio 1: orientamento naturale verticale

Esempio di dispositivo: Pixel 3 XL

Orientamento naturale = verticale
Orientamento attuale = verticale

Rotazione della visualizzazione = 0
Rotazione target = 0

Orientamento naturale = verticale
Orientamento attuale = orizzontale

Rotazione della Rete Display = 90
Rotazione target = 90

Esempio 2: orientamento naturale orizzontale

Esempio di dispositivo: Pixel C

Orientamento naturale = orizzontale
Orientamento attuale = orizzontale

Rotazione della visualizzazione = 0
Rotazione target = 0

Orientamento naturale = orizzontale
Orientamento attuale = verticale

Rotazione della Rete Display = 270
Rotazione target = 270

Rotazione dell'immagine

Quale estremità è? In Android, l'orientamento del sensore è definito come un valore costante, che rappresenta i gradi (0, 90, 180, 270) di rotazione del sensore dalla parte superiore del dispositivo quando quest'ultimo si trova in una posizione naturale. Per tutti i casi nei diagrammi, la rotazione delle immagini descrive come ruotare i dati in senso orario per apparire in posizione verticale.

I seguenti esempi mostrano quale dovrebbe essere la rotazione dell'immagine in base all'orientamento del sensore della fotocamera. Presuppongono inoltre che la rotazione target sia impostata sulla rotazione del display.

Esempio 1: il sensore è ruotato di 90 gradi

Esempio di dispositivo: Pixel 3 XL

Rotazione del display = 0
Orientamento del display = verticale
Rotazione delle immagini = 90

Rotazione del display = 90
Orientamento del display = orizzontale
Rotazione dell'immagine = 0

Esempio 2: il sensore è ruotato di 270 gradi

Esempio di dispositivo: Nexus 5X

Rotazione del display = 0
Orientamento del display = verticale
Rotazione delle immagini = 270

Rotazione del display = 90
Orientamento del display = orizzontale
Rotazione dell'immagine = 180

Esempio 3: il sensore è ruotato di 0 gradi

Esempio di dispositivo: Pixel C (tablet)

Rotazione del display = 0
Orientamento del display = orizzontale
Rotazione dell'immagine = 0

Rotazione del display = 270
Orientamento del display = verticale
Rotazione delle immagini = 90

Calcolo della rotazione di un'immagine

Analisi di immagini

Il dispositivo Analyzer di ImageAnalysis riceve immagini dalla fotocamera sotto forma di ImageProxy. Ogni immagine contiene informazioni sulla rotazione, accessibili tramite:

val rotation = imageProxy.imageInfo.rotationDegrees

Questo valore rappresenta i gradi di rotazione dell'immagine in senso orario per adattarsi alla rotazione target di ImageAnalysis. Nel contesto di un'app per Android, la rotazione target di ImageAnalysis corrisponde in genere all'orientamento dello schermo.

Acquisizione immagine

Un callback è collegato a un'istanza ImageCapture per segnalare quando un risultato di acquisizione è pronto. Il risultato può essere l'immagine acquisita o un errore.

Quando scatti una foto, il callback fornito può essere di uno dei seguenti tipi:

  • OnImageCapturedCallback: riceve un'immagine con accesso in memoria sotto forma di ImageProxy.
  • OnImageSavedCallback: richiamato quando l'immagine acquisita è stata archiviata correttamente nella località specificata da ImageCapture.OutputFileOptions. Le opzioni possono specificare un File, un OutputStream o una località in MediaStore.

La rotazione dell'immagine acquisita, indipendentemente dal formato (ImageProxy, File, OutputStream, MediaStore Uri), rappresenta i gradi di rotazione in base ai quali l'immagine acquisita deve essere ruotata in senso orario per soddisfare la rotazione target di ImageCapture. Anche in questo caso, nel contesto di un'app per Android corrisponde in genere all'orientamento dello schermo.

È possibile recuperare la rotazione dell'immagine acquisita in uno dei seguenti modi:

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

Verificare la rotazione di un'immagine

I casi d'uso ImageAnalysis e ImageCapture ricevono ImageProxy dalla videocamera dopo una richiesta di acquisizione andata a buon fine. Un ImageProxy aggrega un'immagine e le relative informazioni, inclusa la sua rotazione. Queste informazioni sulla rotazione rappresentano i gradi in cui l'immagine deve essere ruotata per corrispondere alla rotazione target del caso d'uso.

Flusso di verifica della rotazione di un'immagine

Linee guida relative alla rotazione target per Image Capture/ImageAnalysis

Poiché per impostazione predefinita molti dispositivi non ruotano in senso verticale o orizzontale invertito, alcune app per Android non supportano questi orientamenti. Il fatto che un'app la supporti o meno, cambia il modo in cui è possibile aggiornare la rotazione target dei casi d'uso.

Di seguito sono riportate due tabelle che definiscono come mantenere sincronizzata la rotazione target dei casi d'uso con la rotazione del display. La prima mostra come farlo supportando tutti e quattro gli orientamenti; la seconda gestisce solo gli orientamenti su cui ruota il dispositivo per impostazione predefinita.

Per scegliere quali linee guida seguire nella tua app:

  1. Verifica se la videocamera della tua app Activity ha un orientamento bloccato, sbloccato o se sostituisce le modifiche alla configurazione dell'orientamento.

  2. Decidi se la fotocamera della tua app Activity deve gestire tutti e quattro gli orientamenti del dispositivo (verticale, verticale, orizzontale e orizzontale invertito) o se deve gestire solo gli orientamenti supportati dal dispositivo su cui è in esecuzione per impostazione predefinita.

Supporta tutti e quattro gli orientamenti

In questa tabella sono riportate alcune linee guida da seguire nei casi in cui il dispositivo non ruota per ruotare in verticale. Lo stesso può essere applicato ai dispositivi che non ruotano in orizzontale.

Scenario Linee guida Modalità finestra singola Modalità schermo diviso multi-finestra
Orientamento sbloccato Configura i casi d'uso ogni volta che viene creato il Activity, ad esempio nel callback onCreate() di Activity.
Usa onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso. In questo modo vengono gestiti i casi in cui il sistema non ricrea l'elemento Activity anche dopo una modifica dell'orientamento, ad esempio quando il dispositivo viene ruotato di 180 gradi. Consente anche di gestire il display con orientamento verticale inverso e, per impostazione predefinita, il dispositivo non ruota per ruotare in verticale. Gestisce anche i casi in cui Activity non viene ricreato quando il dispositivo ruota (ad esempio 90 gradi). Questo accade sui dispositivi con fattori di forma ridotti quando l'app occupa metà dello schermo e sui dispositivi più grandi quando l'app occupa due terzi dello schermo.
(Facoltativo) Imposta la proprietà screenOrientation di Activity su fullSensor nel file AndroidManifest. In questo modo l'interfaccia utente è dritta quando il dispositivo è in modalità verticale inversa e il sistema può ricreare Activity ogni volta che il dispositivo viene ruotato di 90 gradi. Non ha effetto sui dispositivi che non ruotano in modo da invertire l'orientamento verticale per impostazione predefinita. La modalità multi-finestra non è supportata quando il display è con l'orientamento verticale inverso.
Orientamento bloccato Configura i casi d'uso solo una volta, quando Activity viene creato per la prima volta, ad esempio nel callback onCreate() di Activity.
Usa onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso, ad eccezione dell'anteprima. Gestisce anche i casi in cui Activity non viene ricreato quando il dispositivo ruota (ad esempio 90 gradi). Questo accade sui dispositivi con fattori di forma ridotti quando l'app occupa metà dello schermo e sui dispositivi più grandi quando l'app occupa due terzi dello schermo.
Modifiche alla configurazione di orientamento sostituite Configura i casi d'uso solo una volta, quando Activity viene creato per la prima volta, ad esempio nel callback onCreate() di Activity.
Usa onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso. Gestisce anche i casi in cui Activity non viene ricreato quando il dispositivo ruota (ad esempio 90 gradi). Questo accade sui dispositivi con fattori di forma ridotti quando l'app occupa metà dello schermo e sui dispositivi più grandi quando l'app occupa due terzi dello schermo.
(Facoltativo) Imposta la proprietà screenOrientation dell'attività su fullSensor nel file AndroidManifest. Consente di tenere l'interfaccia utente in posizione verticale quando il dispositivo è in verticale al contrario. Non ha effetto sui dispositivi che non ruotano in modo da invertire l'orientamento verticale per impostazione predefinita. La modalità multi-finestra non è supportata quando il display ha un orientamento verticale inverso.

Supporta solo gli orientamenti supportati dal dispositivo

Sono supportati solo gli orientamenti supportati dal dispositivo per impostazione predefinita (che possono o meno includere l'orientamento verticale/orizzontale inverso).

Scenario Linee guida Modalità schermo diviso multi-finestra
Orientamento sbloccato Configura i casi d'uso ogni volta che viene creato il Activity, ad esempio nel callback onCreate() di Activity.
Usa onDisplayChanged() di DisplayListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso, ad esempio quando il dispositivo viene ruotato di 180 gradi. Gestisce anche i casi in cui Activity non viene ricreato quando il dispositivo ruota (ad esempio 90 gradi). Questo accade sui dispositivi con fattori di forma ridotti quando l'app occupa metà dello schermo e sui dispositivi più grandi quando l'app occupa due terzi dello schermo.
Orientamento bloccato Configura i casi d'uso solo una volta, quando Activity viene creato per la prima volta, ad esempio nel callback onCreate() di Activity.
Usa onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso. Gestisce anche i casi in cui Activity non viene ricreato quando il dispositivo ruota (ad esempio 90 gradi). Questo accade sui dispositivi con fattori di forma ridotti quando l'app occupa metà dello schermo e sui dispositivi più grandi quando l'app occupa due terzi dello schermo.
Modifiche alla configurazione di orientamento sostituite Configura i casi d'uso solo una volta, quando Activity viene creato per la prima volta, ad esempio nel callback onCreate() di Activity.
Usa onDisplayChanged() di DisplayListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso, ad esempio quando il dispositivo viene ruotato di 180 gradi. Gestisce anche i casi in cui Activity non viene ricreato quando il dispositivo ruota (ad esempio 90 gradi). Questo accade sui dispositivi con fattori di forma ridotti quando l'app occupa metà dello schermo e sui dispositivi più grandi quando l'app occupa due terzi dello schermo.

Orientamento sbloccato

Un Activity è sbloccato quando il suo orientamento del display (ad esempio verticale o orizzontale) corrisponde all'orientamento fisico del dispositivo, ad eccezione del formato verticale/orizzontale invertito, che alcuni dispositivi non supportano per impostazione predefinita. Per forzare la rotazione del dispositivo in tutti e quattro gli orientamenti, imposta la proprietà screenOrientation di Activity su fullSensor.

In modalità multi-finestra, un dispositivo che non supporta per impostazione predefinita la modalità Verticale/orizzontale invertita non ruoterà in modo da ruotare correttamente l'orientamento verticale/orizzontale, anche se la proprietà screenOrientation è impostata su 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" />

Orientamento bloccato

Un display ha un orientamento bloccato quando rimane nello stesso orientamento (ad esempio verticale o orizzontale) indipendentemente dall'orientamento fisico del dispositivo. A questo scopo, specifica una proprietà screenOrientation di Activity nella relativa dichiarazione nel file AndroidManifest.xml.

Se l'orientamento del display è bloccato, il sistema non elimina né ricrea Activity durante la rotazione del dispositivo.

<!-- The Activity keeps a portrait orientation even as the device rotates. -->
<activity
   android:name=".LockedOrientationActivity"
   android:screenOrientation="portrait" />

Modifiche alla configurazione di orientamento sostituite

Quando un Activity sostituisce le modifiche alla configurazione dell'orientamento, il sistema non lo elimina né lo ricrea quando cambia l'orientamento fisico del dispositivo. Tuttavia, il sistema aggiorna l'interfaccia utente in base all'orientamento fisico 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" />

Configurazione dei casi d'uso della videocamera

Negli scenari descritti sopra, i casi d'uso delle videocamere possono essere configurati subito dopo la creazione di Activity.

Nel caso di un oggetto Activity con orientamento sbloccato, questa configurazione viene eseguita ogni volta che il dispositivo viene ruotato, poiché il sistema distrugge e ricrea il Activity quando cambia l'orientamento. In questo modo, i casi d'uso impostano ogni volta la rotazione target in modo che corrisponda all'orientamento del display per impostazione predefinita.

Nel caso di un oggetto Activity con orientamento bloccato o che sostituisce le modifiche alla configurazione dell'orientamento, questa configurazione viene eseguita una sola volta, quando viene creata per la prima volta l'elemento 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)
   }
}

Configurazione di OrientationEventListener

L'utilizzo di un OrientationEventListener ti consente di aggiornare continuamente la rotazione target dei casi d'uso della fotocamera quando cambia l'orientamento 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()
    }
}

Configurazione di DisplayListener

L'utilizzo di un DisplayListener ti consente di aggiornare la rotazione target dei casi d'uso della videocamera in determinate situazioni, ad esempio quando il sistema non elimina e ricrea il Activity dopo che il dispositivo ruota di 180 gradi.

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