Rotazioni del caso d'uso di CameraX

Questo argomento mostra come configurare i casi d'uso di CameraX all'interno della tua app per ottenere immagini con le informazioni sulla rotazione corrette, che provengano dal caso d'uso ImageAnalysis o ImageCapture. Così:

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

Terminologia

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

Orientamento del display
Si riferisce al lato del dispositivo in posizione verticale e può essere uno di quattro valori: ritratto, orizzontale, ritratto inverso o orizzontale inverso.
Rotazione del display
Si tratta del valore restituito da Display.getRotation() e rappresenta i gradi di rotazione antioraria del dispositivo rispetto al suo orientamento naturale.
Rotazione target
Rappresenta il numero di gradi di rotazione del dispositivo in senso orario per raggiungere il suo orientamento naturale.

Come determinare la rotazione target

Gli esempi riportati di seguito mostrano come determinare la rotazione target per un dispositivo in base al suo orientamento naturale.

Esempio 1: orientamento naturale verticale

Esempio di dispositivo: Pixel 3 XL

Orientamento naturale = Ritratto
Orientamento corrente = Ritratto

Rotazione del display = 0
Rotazione target = 0

Orientamento naturale = Ritratto
Orientamento attuale = Orizzontale

Rotazione del display = 90
Rotazione target = 90

Esempio 2: orientamento naturale orizzontale

Esempio di dispositivo: Pixel C

Orientamento naturale = Orizzontale
Orientamento attuale = Orizzontale

Rotazione del display = 0
Rotazione target = 0

Orientamento naturale = Orizzontale
Orientamento attuale = Ritratto

Rotazione del display = 270
Rotazione target = 270

Rotazione delle immagini

Quale parte è rivolta verso l\'alto? L'orientamento del sensore è definito in Android come valore costante, che rappresenta i gradi (0, 90, 180, 270) di rotazione del sensore rispetto alla parte superiore del dispositivo quando il dispositivo è in una posizione naturale. Per tutti i casi nei diagrammi, la rotazione dell'immagine descrive come devono essere ruotati i dati in senso orario per essere visualizzati in verticale.

Gli esempi seguenti mostrano quale deve essere la rotazione dell'immagine in base all'orientamento del sensore della fotocamera. Inoltre, presuppongono che la rotazione target sia impostata sulla rotazione del display.

Esempio 1: sensore ruotato di 90 gradi

Esempio di dispositivo: Pixel 3 XL

Rotazione del display = 0
Orientamento del display = Ritratto
Rotazione immagine = 90

Rotazione del display = 90
Orientamento del display = Orizzontale
Rotazione immagine = 0

Esempio 2: sensore ruotato di 270 gradi

Esempio di dispositivo: Nexus 5X

Rotazione del display = 0
Orientamento del display = Ritratto
Rotazione immagine = 270

Rotazione del display = 90
Orientamento del display = Orizzontale
Rotazione immagine = 180

Esempio 3: sensore ruotato di 0 gradi

Esempio di dispositivo: Pixel C (tablet)

Rotazione del display = 0
Orientamento del display = Orizzontale
Rotazione immagine = 0

Rotazione del display = 270
Orientamento del display = Ritratto
Rotazione immagine = 90

Calcolo della rotazione di un'immagine

ImageAnalysis

Il Analyzer di ImageAnalysis riceve le 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 allinearla alla rotazione target di ImageAnalysis. Nel contesto di un'app Android, la rotazione target di ImageAnalysis corrisponde in genere all'orientamento dello schermo.

ImageCapture

Un callback è associato 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: viene invocato quando l'immagine acquisita è stata memorizzata correttamente nella posizione specificata da ImageCapture.OutputFileOptions. Le opzioni possono specificare un File, un OutputStream o una posizione in MediaStore.

La rotazione dell'immagine acquisita, indipendentemente dal formato (ImageProxy, File, OutputStream, MediaStore Uri), rappresenta i gradi di rotazione con la quale l'immagine acquisita deve essere ruotata in senso orario per corrispondere alla rotazione target di ImageCapture, che, di nuovo, nel contesto di un'app per Android, tipicamente corrisponde all'orientamento dello schermo.

Il recupero della rotazione dell'immagine acquisita può essere eseguito 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 racchiude un'immagine e le informazioni relative, inclusa la rotazione. Queste informazioni sulla rotazione rappresentano i gradi di rotazione dell'immagine in modo che corrispondano alla rotazione target del caso d'uso.

Flusso di verifica della rotazione di un'immagine

Linee guida per la rotazione del target di ImageCapture/ImageAnalysis

Poiché molti dispositivi non ruotano in modalità Ritratto o Orizzontale capovolto per impostazione predefinita, alcune app per Android non supportano questi orientamenti. Il fatto che un'app supporti o meno questa funzionalità cambia il modo in cui può essere aggiornata la rotazione dei 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. Il primo mostra come farlo supportando tutti i quattro gli orientamenti, mentre il secondo gestisce solo gli orientamenti a cui il dispositivo ruota per impostazione predefinita.

Per scegliere quali linee guida seguire nella tua app:

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

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

Supporta tutti e quattro gli orientamenti

Questa tabella riporta alcune linee guida da seguire nei casi in cui il dispositivo non ruoti in modalità Ritratto inverso. Lo stesso vale per i dispositivi che non ruotano in modalità Orizzontale inversa.

Scenario Linee guida Modalità finestra singola Modalità schermo diviso multi-finestra
Orientamento sbloccato Configura i casi d'uso ogni volta che viene creato Activity, ad esempio nel callback onCreate() di Activity.
Utilizza onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione del target dei casi d'uso. Questo gestisce i casi in cui il sistema non rielabora il Activity anche dopo una modifica dell'orientamento, ad esempio quando il dispositivo viene ruotato di 180 gradi. Gestisce anche il caso in cui il display sia in orientamento verticale capovolto e il dispositivo non ruoti in verticale capovolto per impostazione predefinita. Gestisce anche i casi in cui Activity non viene nuovamente creato quando il dispositivo ruota (ad esempio di 90 gradi). Questo accade su dispositivi con fattore di forma ridotto quando l'app occupa metà dello schermo e su 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 è verticale quando il dispositivo è in ritratto capovolto e consente al sistema di ricreare Activity ogni volta che il dispositivo viene ruotato di 90 gradi. Non ha alcun effetto sui dispositivi che non ruotano in modalità Ritratto capovolto per impostazione predefinita. La modalità multi-finestra non è supportata quando il display è in orientamento ritratto opposto.
Orientamento bloccato Configura i casi d'uso una sola volta, quando viene creato per la prima volta il Activity, ad esempio nel callback onCreate() del Activity.
Utilizza onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione target dei casi d'uso, ad eccezione di Anteprima. Gestisce anche i casi in cui Activity non viene nuovamente creato quando il dispositivo ruota (ad esempio di 90 gradi). Questo accade su dispositivi con fattore di forma ridotto quando l'app occupa metà dello schermo e su dispositivi più grandi quando l'app occupa due terzi dello schermo.
Orientamento configChanges sostituito Configura i casi d'uso una sola volta, quando viene creato per la prima volta il Activity, ad esempio nel callback onCreate() del Activity.
Utilizza onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione del target dei casi d'uso. Gestisce anche i casi in cui Activity non viene nuovamente creato quando il dispositivo ruota (ad esempio di 90 gradi). Questo accade su dispositivi con fattore di forma ridotto quando l'app occupa metà dello schermo e su dispositivi più grandi quando l'app occupa due terzi dello schermo.
(Facoltativo) Imposta la proprietà screenOrientation dell'attività su fullSensor nel file AndroidManifest. Consente all'interfaccia utente di essere in verticale quando il dispositivo è in modalità Ritratto inversa. Non ha alcun effetto sui dispositivi che non ruotano in modalità Ritratto capovolto per impostazione predefinita. La modalità multi-finestra non è supportata quando il display è in orientamento verticale opposto.

Supporta solo gli orientamenti supportati dal dispositivo

Supporta solo gli orientamenti supportati dal dispositivo per impostazione predefinita (che possono o meno includere verticale/orizzontale capovolto).

Scenario Linee guida Modalità schermo diviso multi-finestra
Orientamento sbloccato Configura i casi d'uso ogni volta che viene creato Activity, ad esempio nel callback onCreate() di Activity.
Utilizza 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 nuovamente creato quando il dispositivo ruota (ad esempio di 90 gradi). Questo accade su dispositivi con fattore di forma ridotto quando l'app occupa metà dello schermo e su dispositivi più grandi quando l'app occupa due terzi dello schermo.
Orientamento bloccato Configura i casi d'uso una sola volta, quando viene creato per la prima volta il Activity, ad esempio nel callback onCreate() del Activity.
Utilizza onOrientationChanged() di OrientationEventListener. All'interno del callback, aggiorna la rotazione del target dei casi d'uso. Gestisce anche i casi in cui Activity non viene nuovamente creato quando il dispositivo ruota (ad esempio di 90 gradi). Questo accade su dispositivi con fattore di forma ridotto quando l'app occupa metà dello schermo e su dispositivi più grandi quando l'app occupa due terzi dello schermo.
Orientamento configChanges sostituito Configura i casi d'uso una sola volta, quando viene creato per la prima volta il Activity, ad esempio nel callback onCreate() del Activity.
Utilizza 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 nuovamente creato quando il dispositivo ruota (ad esempio di 90 gradi). Questo accade su dispositivi con fattore di forma ridotto quando l'app occupa metà dello schermo e su dispositivi più grandi quando l'app occupa due terzi dello schermo.

Orientamento sbloccato

Un Activity ha un orientamento sbloccato quando l'orientamento del display (ad esempio verticale o orizzontale) corrisponde all'orientamento fisico del dispositivo, con l'eccezione dell'orientamento verticale/orizzontale inverso, 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 l'orientamento ritratto/paesaggio capovolto per impostazione predefinita non ruoterà in questo modo, 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. Per farlo, specifica la proprietà screenOrientation di un Activity all'interno della relativa dichiarazione nel file AndroidManifest.xml.

Quando l'orientamento del display è bloccato, il sistema non distrugge e rielabora il Activity quando il dispositivo viene ruotato.

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

Modifiche alla configurazione dell'orientamento sostituite

Quando un Activity sostituisce le modifiche alla configurazione dell'orientamento, il sistema non le distrugge e non le 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 della videocamera possono essere configurati al momento della prima creazione di Activity.

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

Nel caso di un Activity con un'opzione di blocco dell'orientamento o che sostituisce le modifiche alla configurazione dell'orientamento, questa configurazione viene eseguita una sola volta, quando viene creato il 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 di destinazione dei casi d'uso della fotocamera man mano che l'orientamento del dispositivo cambia.

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 distrugge e ricrea il Activity dopo la rotazione del dispositivo 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)
    }
}