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'utilisationImageAnalysis
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 Rotation de l'écran = 0 |
|
Orientation naturelle = Portrait Rotation de l'écran = 90 |
Exemple 2 : orientation naturelle paysage
Exemple d'appareil : Pixel C | |
---|---|
Orientation naturelle = Paysage Rotation de l'écran = 0 |
|
Orientation naturelle = Paysage Rotation de l'écran = 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 |
|
Rotation de l'écran = 90 |
Exemple 2 : rotation du capteur de 270 degrés
Exemple d'appareil : Nexus 5X | |
---|---|
Rotation de l'écran = 0 |
|
Rotation de l'écran = 90 |
Exemple 3 : rotation du capteur de 0 degré
Exemple d'appareil : Pixel C (tablette) | |
---|---|
Rotation de l'écran = 0 |
|
Rotation de l'écran = 270 |
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'unImageProxy
.OnImageSavedCallback
: appelé lorsque la photo a bien été stockée à l'emplacement spécifié parImageCapture.OutputFileOptions
. Les options peuvent spécifier unFile
, unOutputStream
ou un emplacement dansMediaStore
.
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.
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 :
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.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) } }