Rotations des cas d'utilisation de CameraX

Ce sujet explique comment configurer les cas d'utilisation de CameraX dans votre application afin d'obtenir les images avec les informations de rotation correctes, qu'elles proviennent du cas d'utilisation ImageAnalysis ou ImageCapture. Comme suit :

  • L'Analyzer du cas d'utilisation ImageAnalysis doit recevoir des cadres avec une rotation correcte.
  • Le cas d'utilisation ImageCapture doit prendre des photos avec une rotation correcte.

Terminologie

Ce sujet utilise la terminologie suivante. Il est donc important de comprendre la signification de chaque terme :

Orientation de l'écran
Cela fait référence au côté de l'appareil en position verticale. Quatre valeurs sont possibles : portrait, paysage, portrait inversé ou paysage inversé.
Rotation de l'écran
Il s'agit de la valeur renvoyée par Display.getRotation(). Elle représente les degrés de rotation de l'appareil dans le sens inverse des aiguilles d'une montre par rapport à son orientation naturelle.
Rotation cible
Cela représente le nombre de degrés de rotation de l'appareil dans le sens des aiguilles d'une montre nécessaire permettant d'atteindre son orientation naturelle.

Déterminer la rotation cible

Les exemples suivants indiquent comment déterminer la rotation cible d'un appareil en fonction de son orientation naturelle.

Exemple 1 : orientation naturelle portrait

Exemple d'appareil : Pixel 3 XL

Orientation naturelle = Portrait
Orientation actuelle = Portrait

Rotation de l'écran = 0
Rotation cible = 0

Orientation naturelle = Portrait
Orientation actuelle = Paysage

Rotation de l'écran = 90
Rotation cible = 90

Exemple 2 : orientation naturelle paysage

Exemple d'appareil : Pixel C

Orientation naturelle = Paysage
Orientation actuelle = Paysage

Rotation de l'écran = 0
Rotation cible = 0

Orientation naturelle = Paysage
Orientation actuelle = Portrait

Rotation de l'écran = 270
Rotation cible = 270

Rotation de l'image

Quel côté est le sommet ? Dans Android, l'orientation du capteur est définie comme une valeur constante, qui représente les degrés (0, 90, 180 et 270) de rotation du capteur par rapport au sommet de l'appareil lorsque celui-ci est en position naturelle. Pour tous les cas présentés dans les diagrammes, la rotation de l'image décrit la méthode de rotation dans le sens des aiguilles d'une montre appliquée aux données pour qu'elles apparaissent à la verticale.

Les exemples suivants illustrent la rotation de l'image en fonction de l'orientation du capteur de l'appareil photo. Ils partent également du principe que la rotation cible est définie sur la rotation de l'écran.

Exemple 1 : rotation du capteur de 90 degrés

Exemple d'appareil : Pixel 3 XL

Rotation de l'écran = 0
Orientation de l'écran = Portrait
Rotation de l'image = 90

Rotation de l'écran = 90
Orientation de l'écran = Paysage
Rotation de l'image = 0

Exemple 2 : rotation du capteur de 270 degrés

Exemple d'appareil : Nexus 5X

Rotation de l'écran = 0
Orientation de l'écran = Portrait
Rotation de l'image = 270

Rotation de l'écran = 90
Orientation de l'écran = Paysage
Rotation de l'image = 180

Exemple 3 : rotation du capteur de 0 degré

Exemple d'appareil : Pixel C (tablette)

Rotation de l'écran = 0
Orientation de l'écran = Paysage
Rotation de l'image = 0

Rotation de l'écran = 270
Orientation de l'écran = Portrait
Rotation de l'image = 90

Calculer la rotation d'une image

"ImageAnalysis"

L'Analyzer de ImageAnalysis reçoit des images de l'appareil photo sous la forme de ImageProxy. Chaque image contient des informations de rotation, accessibles via :

val rotation = imageProxy.imageInfo.rotationDegrees

Cette valeur représente les degrés de rotation de l'image dans le sens des aiguilles d'une montre requis pour correspondre à la rotation cible de ImageAnalysis. Dans le cas d'une application Android, la rotation cible de ImageAnalysis correspond généralement à l'orientation de l'écran.

"ImageCapture"

Un rappel est associé à une instance ImageCapture pour signaler qu'un résultat de prise de vue est prêt. Le résultat peut être une photo ou une erreur.

Lorsque vous prenez une photo, le rappel fourni peut être de l'un des types suivants :

  • OnImageCapturedCallback : reçoit une image avec un accès en mémoire sous la forme d'un ImageProxy.
  • OnImageSavedCallback : appelé lorsque la photo a bien été stockée à l'emplacement spécifié par ImageCapture.OutputFileOptions. Les options peuvent spécifier un File, un OutputStream ou un emplacement dans MediaStore.

La rotation de la photo, quel que soit son format (ImageProxy, File, OutputStream, MediaStore Uri), représente les degrés de rotation nécessaires pour effectuer une rotation de la photo dans le sens des aiguilles d'une montre permettant de correspondre à la rotation cible de ImageCapture, qui correspond généralement, dans le cas d'une application Android, à l'orientation de l'écran.

Vous pouvez récupérer la rotation de la photo de l'une des manières suivantes :

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

Vérifier la rotation d'une image

Les cas d'utilisation ImageAnalysis et ImageCapture reçoivent des ImageProxy de l'appareil photo après une demande de capture réussie. Un ImageProxy encapsule une image et ses informations, y compris sa rotation. Ces informations de rotation représentent les degrés de rotation de l'image nécessaires pour correspondre à la rotation cible du cas d'utilisation.

Flux de vérification de la rotation d'une image

Consignes de rotation cible "ImageCapture/ImageAnalysis"

Étant donné que de nombreux appareils n'effectuent pas de rotation en mode portrait ou paysage inversé par défaut, certaines applications Android ne prennent pas en charge ces orientations. La prise en charge ou non de ces orientations par une application modifie la manière dont la rotation cible des cas d'utilisation peut être mise à jour.

Vous trouverez ci-dessous deux tableaux indiquant comment synchroniser la rotation cible des cas d'utilisation avec la rotation de l'écran. Le premier explique comment y parvenir tout en prenant en charge les quatre orientations. Le deuxième ne gère que les orientations de rotation par défaut de l'appareil.

Pour choisir les consignes à suivre dans votre application :

  1. Vérifiez si l'Activity de l'appareil photo de votre application présente une orientation verrouillée, une orientation déverrouillée ou si elle remplace les modifications de configuration de l'orientation.

  2. Déterminez si l'Activity de l'appareil photo de votre application doit gérer les quatre orientations d'appareil (portrait, portrait inversé, paysage et paysage inversé) ou si elle ne doit gérer que les orientations prises en charge par défaut par l'appareil sur lequel elle est exécutée.

Prise en charge des quatre orientations

Ce tableau présente certaines consignes à suivre lorsque l'appareil n'effectue pas de rotation en mode portrait inversé. Il en va de même pour les appareils qui n'effectuent pas de rotation en mode paysage inversé.

Scénario Consignes Mode fenêtre unique Mode Écran partagé multifenêtre
Orientation déverrouillée Configurez les cas d'utilisation chaque fois que l'Activity est créée, par exemple dans le rappel onCreate() d'Activity.
Utilisez onOrientationChanged() de OrientationEventListener. Dans le rappel, mettez à jour la rotation cible des cas d'utilisation. Cela permet de gérer les cas dans lesquels le système ne recrée pas l'Activity même après un changement d'orientation, par exemple lors d'une rotation de 180 degrés de l'appareil. Permet également de gérer les cas dans lesquels l'écran présente une orientation en mode portrait inversé et l'appareil n'effectue pas de rotation en mode portrait inversé par défaut. Permet également de gérer les cas dans lesquels l'Activity n'est pas recréé lorsque l'appareil effectue une rotation (90 degrés, par exemple). Cela survient sur les appareils à petit facteur de forme lorsque l'application occupe la moitié de l'écran et sur les appareils plus grands lorsque l'application occupe les deux tiers de l'écran.
Facultatif : définissez la propriété screenOrientation d'Activity sur fullSensor dans le fichier AndroidManifest. Ainsi, l'interface utilisateur peut être affichée à la verticale lorsque l'appareil est en mode portrait inversé et l'Activity peut être recréée par le système lors d'une rotation de 90 degrés de l'appareil. Cette option n'a aucune incidence sur les appareils qui n'effectuent pas de rotation en mode portrait inversé par défaut. Le mode multifenêtre n'est pas pris en charge lorsque l'orientation de l'écran est en mode portrait inversé.
Orientation verrouillée Configurez les cas d'utilisation une seule fois, lorsque l'Activity est créée pour la première fois, par exemple dans le rappel onCreate() d'Activity.
Utilisez onOrientationChanged() de OrientationEventListener. Dans le rappel, mettez à jour la rotation cible des cas d'utilisation, à l'exception de Preview. Permet également de gérer les cas dans lesquels l'Activity n'est pas recréé lorsque l'appareil effectue une rotation (90 degrés, par exemple). Cela survient sur les appareils à petit facteur de forme lorsque l'application occupe la moitié de l'écran et sur les appareils plus grands lorsque l'application occupe les deux tiers de l'écran.
Remplacement des "configChanges" de l'orientation Configurez les cas d'utilisation une seule fois, lorsque l'Activity est créée pour la première fois, par exemple dans le rappel onCreate() d'Activity.
Utilisez onOrientationChanged() de OrientationEventListener. Dans le rappel, mettez à jour la rotation cible des cas d'utilisation. Permet également de gérer les cas dans lesquels l'Activity n'est pas recréé lorsque l'appareil effectue une rotation (90 degrés, par exemple). Cela survient sur les appareils à petit facteur de forme lorsque l'application occupe la moitié de l'écran et sur les appareils plus grands lorsque l'application occupe les deux tiers de l'écran.
Facultatif : définissez la propriété "screenOrientation" d'Activity sur "fullSensor" dans le fichier "AndroidManifest". Permet d'afficher l'interface utilisateur à la verticale lorsque l'appareil est en mode portrait inversé. Cette option n'a aucune incidence sur les appareils qui n'effectuent pas de rotation en mode portrait inversé par défaut. Le mode multifenêtre n'est pas pris en charge lorsque l'orientation de l'écran est en mode portrait inversé.

Prise en charge des orientations compatibles avec l'appareil uniquement

Ne prenez en charge que les orientations compatibles par défaut avec l'appareil (qui peuvent ou non inclure le mode portrait ou paysage inversé).

Scénario Consignes Mode Écran partagé multifenêtre
Orientation déverrouillée Configurez les cas d'utilisation chaque fois que l'Activity est créée, par exemple dans le rappel onCreate() d'Activity.
Utilisez onDisplayChanged() de DisplayListener. Dans le rappel, mettez à jour la rotation cible des cas d'utilisation (par exemple, lors d'une rotation de 180 degrés de l'appareil). Permet également de gérer les cas dans lesquels l'Activity n'est pas recréé lorsque l'appareil effectue une rotation (90 degrés, par exemple). Cela survient sur les appareils à petit facteur de forme lorsque l'application occupe la moitié de l'écran et sur les appareils plus grands lorsque l'application occupe les deux tiers de l'écran.
Orientation verrouillée Configurez les cas d'utilisation une seule fois, lorsque l'Activity est créée pour la première fois, par exemple dans le rappel onCreate() d'Activity.
Utilisez onOrientationChanged() de OrientationEventListener. Dans le rappel, mettez à jour la rotation cible des cas d'utilisation. Permet également de gérer les cas dans lesquels l'Activity n'est pas recréé lorsque l'appareil effectue une rotation (90 degrés, par exemple). Cela survient sur les appareils à petit facteur de forme lorsque l'application occupe la moitié de l'écran et sur les appareils plus grands lorsque l'application occupe les deux tiers de l'écran.
Remplacement des "configChanges" de l'orientation Configurez les cas d'utilisation une seule fois, lorsque l'Activity est créée pour la première fois, par exemple dans le rappel onCreate() d'Activity.
Utilisez onDisplayChanged() de DisplayListener. Dans le rappel, mettez à jour la rotation cible des cas d'utilisation (par exemple, lors d'une rotation de 180 degrés de l'appareil). Permet également de gérer les cas dans lesquels l'Activity n'est pas recréé lorsque l'appareil effectue une rotation (90 degrés, par exemple). Cela survient sur les appareils à petit facteur de forme lorsque l'application occupe la moitié de l'écran et sur les appareils plus grands lorsque l'application occupe les deux tiers de l'écran.

Orientation déverrouillée

L'orientation d'une Activity est déverrouillée lorsque l'orientation de l'écran (par exemple, portrait ou paysage) correspond à l'orientation physique de l'appareil, à l'exception du mode portrait ou paysage inversé que certains appareils ne prennent pas en charge par défaut. Pour forcer l'appareil à effectuer une rotation vers les quatre orientations, définissez la propriété screenOrientation d'Activity sur fullSensor.

En mode multifenêtre, un appareil qui ne prend pas en charge le mode portrait ou paysage inversé par défaut n'effectue pas de rotation en mode portrait ou paysage inversé, même si la propriété screenOrientation est définie sur 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" />

Orientation verrouillée

L'orientation d'un écran est verrouillée lorsqu'il conserve la même orientation (portrait ou paysage, par exemple), quelle que soit l'orientation physique de l'appareil. Pour ce faire, spécifiez la propriété screenOrientation d'une Activity dans sa déclaration dans le fichier AndroidManifest.xml.

Lorsque l'orientation de l'écran est verrouillée, le système ne détruit pas et ne recrée pas l'Activity lors de la rotation de l'appareil.

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

Remplacement des modifications de configuration de l'orientation

Lorsqu'une Activity remplace les modifications de la configuration de l'orientation, le système ne la détruit pas et ne la recrée pas lors de la modification de l'orientation physique de l'appareil. Le système met à jour l'interface utilisateur pour qu'elle corresponde à l'orientation physique de l'appareil.

<!-- 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" />

Configuration des cas d'utilisation de l'appareil photo

Dans les scénarios décrits ci-dessus, les cas d'utilisation de l'appareil photo peuvent être configurés lorsque l'Activity est créée pour la première fois.

Dans le cas d'une Activity dont l'orientation est déverrouillée, cette configuration est appliquée à chaque rotation de l'appareil, car le système détruit et recrée l'Activity lors des modifications de l'orientation. Par conséquent, les cas d'utilisation définissent à chaque fois leur rotation cible pour qu'elle corresponde à l'orientation de l'écran par défaut.

Dans le cas d'une Activity dont l'orientation est verrouillée ou qui remplace les modifications de la configuration de l'orientation, cette configuration est effectuée une seule fois lorsque l'Activity est créée pour la première fois.

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

Configuration de "OrientationEventListener"

L'utilisation d'un OrientationEventListener vous permet de mettre à jour en continu la rotation cible des cas d'utilisation de l'appareil photo lorsque l'orientation de l'appareil est modifiée.

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

Configuration de "DisplayListener"

L'utilisation d'un DisplayListener vous permet de mettre à jour la rotation cible des cas d'utilisation de l'appareil photo dans certaines situations. Par exemple, lorsque le système ne détruit pas et ne recrée pas l'Activity après une rotation de 180 degrés de l'appareil.

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