Si votre application Android utilise des caméras, vous devez tenir compte de certains aspects spécifiques lors de la gestion des orientations. Ce document suppose que vous comprenez les concepts de base de l'API camera2 d'Android. Pour obtenir une présentation de camera2, vous pouvez consulter notre article de blog ou notre résumé. Nous vous recommandons également d'essayer d'écrire une application d'appareil photo avant de consulter ce document.
Arrière-plan
La gestion des orientations dans les applications d'appareil photo Android est délicate et doit tenir compte des facteurs suivants :
- Orientation naturelle : orientation de l'écran lorsque l'appareil est en position "normale" pour sa conception (généralement l'orientation portrait pour les téléphones mobiles et l'orientation paysage pour les ordinateurs portables).
- Orientation du capteur : orientation du capteur physiquement monté sur l'appareil.
- Rotation de l'écran : rotation physique de l'appareil par rapport à son orientation naturelle.
- Taille du viseur : taille du viseur utilisé pour afficher l'aperçu de l'appareil photo.
- Taille de l'image produite par la caméra.
Ces facteurs combinés introduisent un grand nombre de configurations possibles pour l'UI et l'aperçu des applis d'appareil photo. Ce document vise à montrer aux développeurs comment parcourir ces orientations et gérer correctement les orientations de l'appareil photo dans les applications Android.
Pour simplifier les choses, supposons que tous les exemples impliquent une caméra orientée vers l'arrière, sauf indication contraire. De plus, toutes les photos suivantes sont simulées pour rendre les illustrations plus claires.
À propos des orientations
Orientation naturelle
L'orientation naturelle est définie comme l'orientation de l'écran lorsque l'appareil est dans la position dans laquelle il est normalement censé se trouver. Pour les téléphones, l'orientation naturelle est souvent le mode Portrait. En d'autres termes, les téléphones ont une largeur plus petite et une hauteur plus grande. Pour les ordinateurs portables, l'orientation naturelle est le mode paysage, ce qui signifie que leur largeur est plus importante que leur hauteur. Les tablettes sont un peu plus complexes : elles peuvent être en mode portrait ou paysage.
Orientation du capteur
Pour être plus précis, l'orientation du capteur est mesurée en degrés de rotation dans le sens des aiguilles d'une montre nécessaires pour que l'image de sortie du capteur corresponde à l'orientation naturelle de l'appareil. En d'autres termes, l'orientation du capteur correspond au nombre de degrés de rotation du capteur dans le sens inverse des aiguilles d'une montre avant son montage sur l'appareil. Lorsque vous regardez l'écran, la rotation semble se faire dans le sens des aiguilles d'une montre, car le capteur de la caméra arrière est installé à l'arrière de l'appareil.
Selon la Définition de compatibilité Android 10 7.5.5 Orientation de la caméra, les caméras avant et arrière "DOIVENT être orientées de sorte à faire correspondre la dimension longue de la caméra à la dimension longue de l'écran".
Les tampons de sortie des caméras sont au format paysage. Étant donné que l'orientation naturelle des téléphones est généralement portrait, l'orientation du capteur est généralement de 90 ou 270 degrés par rapport à l'orientation naturelle afin que le côté long du tampon de sortie corresponde au côté long de l'écran. L'orientation du capteur est différente pour les appareils dont l'orientation naturelle est le mode paysage, comme les Chromebooks. Sur ces appareils, les capteurs d'image sont à nouveau placés de sorte que le côté long du tampon de sortie corresponde au côté long de l'écran. Comme les deux sont au format Paysage, les orientations correspondent et l'orientation du capteur est de 0 ou 180 degrés.
Les illustrations suivantes montrent comment les éléments s'affichent du point de vue d'un observateur qui regarde l'écran de l'appareil :
Prenons l'exemple suivant :
| Téléphone | Ordinateur portable |
|---|---|
![]() |
![]() |
Comme l'orientation du capteur est généralement de 90 ou 270 degrés sur les téléphones, sans tenir compte de l'orientation du capteur, les images que vous obtiendriez ressembleraient à ceci :
| Téléphone | Ordinateur portable |
|---|---|
![]() |
![]() |
Supposons que l'orientation du capteur dans le sens inverse des aiguilles d'une montre soit stockée dans la variable sensorOrientation. Pour compenser l'orientation du capteur, vous devez faire pivoter les tampons de sortie de `sensorOrientation` dans le sens des aiguilles d'une montre pour réaligner l'orientation sur l'orientation naturelle de l'appareil.
Dans Android, les applications peuvent utiliser TextureView ou SurfaceView pour afficher l'aperçu de leur caméra. Les deux peuvent gérer l'orientation du capteur si les applications les utilisent correctement. Nous vous expliquerons comment tenir compte de l'orientation du capteur dans les sections suivantes.
Rotation de l'écran
La rotation de l'écran est formellement définie par la rotation des éléments graphiques dessinés sur l'écran, qui est dans le sens opposé à la rotation physique de l'appareil par rapport à son orientation naturelle. Les sections suivantes supposent que les rotations d'écran sont toutes des multiples de 90. Si vous récupérez la rotation de l'écran en degrés absolus, arrondissez-la à la valeur la plus proche parmi {0, 90, 180, 270}.
Dans les sections suivantes, "orientation de l'écran" fait référence à la position physique de l'appareil (paysage ou portrait) et est différente de la "rotation de l'écran".
Supposons que vous fassiez pivoter les appareils de 90 degrés dans le sens inverse des aiguilles d'une montre par rapport à leur position précédente, comme illustré dans la figure suivante :
En supposant que les tampons de sortie sont déjà pivotés en fonction de l'orientation du capteur, vous disposeriez alors des tampons de sortie suivants :
| Téléphone | Ordinateur portable |
|---|---|
![]() |
![]() |
Si la rotation de l'écran est stockée dans la variable displayRotation, pour obtenir l'image correcte, vous devez faire pivoter les tampons de sortie de displayRotation dans le sens inverse des aiguilles d'une montre.
Pour les caméras avant, la rotation de l'écran agit sur les tampons d'image dans le sens opposé à celui de l'écran. Si vous utilisez une caméra avant, vous devez faire pivoter les tampons dans le sens des aiguilles d'une montre en fonction de la rotation de l'écran.
Mises en garde
La rotation de l'écran mesure la rotation de l'appareil dans le sens inverse des aiguilles d'une montre. Cela ne s'applique pas à toutes les API d'orientation/de rotation.
Par exemple,
-
Si vous utilisez
Display#getRotation(), vous obtiendrez la rotation dans le sens inverse des aiguilles d'une montre mentionnée dans ce document. - Si vous utilisez OrientationEventListener#onOrientationChanged(int), vous obtiendrez plutôt la rotation dans le sens des aiguilles d'une montre.
Il est important de noter que la rotation de l'écran est relative à l'orientation naturelle. Par exemple, si vous faites pivoter physiquement un téléphone de 90 ou 270 degrés, l'écran s'affiche en mode Paysage. En comparaison, vous obtiendriez un écran au format portrait si vous faisiez pivoter un ordinateur portable du même angle. Les applications doivent toujours garder cela à l'esprit et ne jamais faire d'hypothèses sur l'orientation naturelle d'un appareil.
Exemples
Utilisons les chiffres précédents pour illustrer les orientations et les rotations.
| Téléphone | Ordinateur portable |
|---|---|
| Orientation naturelle = Portrait | Orientation naturelle = Paysage |
| Orientation du capteur = 90 | Orientation du capteur = 0 |
| Rotation de l'écran = 0 | Rotation de l'écran = 0 |
| Orientation de l'écran = Portrait | Orientation de l'écran = Paysage |
| Téléphone | Ordinateur portable |
|---|---|
| Orientation naturelle = Portrait | Orientation naturelle = Paysage |
| Orientation du capteur = 90 | Orientation du capteur = 0 |
| Rotation de l'écran = 90 | Rotation de l'écran = 90 |
| Orientation de l'écran = Paysage | Orientation de l'écran = Portrait |
Taille du viseur
Les applications doivent toujours redimensionner le viseur en fonction de l'orientation, de la rotation et de la résolution de l'écran. En règle générale, les applications doivent faire en sorte que l'orientation du viseur soit identique à l'orientation actuelle de l'écran. En d'autres termes, les applications doivent aligner le bord long du viseur sur le bord long de l'écran.
Taille de sortie des images par caméra
Lorsque vous choisissez la taille de sortie de l'image pour l'aperçu, vous devez choisir une taille égale ou légèrement supérieure à celle du viseur, dans la mesure du possible. En général, vous ne souhaitez pas que les tampons de sortie soient mis à l'échelle, car cela entraînerait une pixellisation. Vous ne devez pas non plus choisir une taille trop grande, car cela pourrait réduire les performances et consommer plus de batterie.
Orientation JPEG
Commençons par une situation courante : la capture d'une photo au format JPEG. Dans l'API camera2, vous pouvez transmettre JPEG_ORIENTATION dans la requête de capture pour spécifier le degré de rotation dans le sens des aiguilles d'une montre de vos fichiers JPEG de sortie.
Voici un bref récapitulatif de ce que nous avons mentionné :
-
Pour gérer l'orientation du capteur, vous devez faire pivoter le tampon d'image de
sensorOrientationdans le sens des aiguilles d'une montre. -
Pour gérer la rotation de l'écran, vous devez faire pivoter un tampon de
displayRotationdans le sens inverse des aiguilles d'une montre pour les caméras arrière et dans le sens des aiguilles d'une montre pour les caméras avant.
En additionnant les deux facteurs, le montant de la rotation dans le sens des aiguilles d'une montre est
-
sensorOrientation - displayRotationpour les caméras arrière. -
sensorOrientation + displayRotationpour les caméras avant.
Vous trouverez un exemple de code pour cette logique dans la documentation JPEG_ORIENTATION. Notez que deviceOrientation dans l'exemple de code de la documentation utilise la rotation de l'appareil dans le sens des aiguilles d'une montre. Les signes de la rotation de l'écran sont donc inversés.
Aperçu
Qu'en est-il de l'aperçu de la caméra ? Une application peut afficher un aperçu de l'appareil photo de deux manières principales : SurfaceView et TextureView. Chacun d'eux nécessite une approche différente pour gérer correctement l'orientation.
SurfaceView
SurfaceView est généralement recommandé pour les aperçus de caméra, à condition que vous n'ayez pas besoin de traiter ni d'animer les tampons d'aperçu. Il est plus performant et moins gourmand en ressources que TextureView.
SurfaceView est également relativement plus facile à organiser. Vous n'avez qu'à vous soucier du format de la SurfaceView sur laquelle vous affichez l'aperçu de l'appareil photo.
Source
Sous SurfaceView, la plate-forme Android fait pivoter les tampons de sortie pour qu'ils correspondent à l'orientation de l'écran de l'appareil. En d'autres termes, il tient compte à la fois de l'orientation du capteur et de la rotation de l'écran. En d'autres termes, lorsque notre écran est en mode paysage, l'aperçu est également en mode paysage, et inversement pour le mode portrait.
Cela est illustré dans le tableau suivant. Il est important de noter que la rotation de l'écran à elle seule ne détermine pas l'orientation de la source.
| Rotation de l'écran | Téléphone (orientation naturelle = portrait) | Ordinateur portable (orientation naturelle = paysage) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180 | ![]() |
![]() |
| 270 | ![]() |
![]() |
Mise en page
Comme vous pouvez le voir, SurfaceView gère déjà certaines des choses délicates pour nous. Vous devez maintenant tenir compte de la taille du viseur ou de la taille de l'aperçu que vous souhaitez afficher à l'écran. SurfaceView met automatiquement à l'échelle la mémoire tampon source pour l'adapter à ses dimensions. Vous devez vous assurer que les proportions du viseur sont identiques à celles du sourcebuffer. Par exemple, si vous essayez d'adapter un aperçu au format portrait dans une SurfaceView au format paysage, vous obtiendrez une image déformée comme celle-ci :
En général, le format (c'est-à-dire largeur/hauteur) du viseur doit être identique à celui de la source. Si vous ne souhaitez pas rogner l'image dans le viseur (c'est-à-dire couper certains pixels pour corriger l'affichage), vous devez tenir compte de deux cas : lorsque aspectRatioActivity est supérieur à aspectRatioSource et lorsqu'il est inférieur ou égal à aspectRatioSource.
aspectRatioActivity > aspectRatioSource
Vous pouvez considérer que le boîtier est "plus large" que l'activité. Prenons l'exemple d'une activité au format 16:9 et d'une source au format 4:3.
aspectRatioActivity = 16/9 ≈ 1.78 aspectRatioSource = 4/3 ≈ 1.33
Tout d'abord, vous devez également définir le format 4:3 pour votre viseur. Vous devez ensuite ajuster la source et le viseur dans l'activité comme suit :
Dans ce cas, vous devez faire correspondre la hauteur du viseur à celle de l'activité, tout en veillant à ce que le format du viseur soit identique à celui de la source. Le pseudo-code est le suivant :
viewfinderHeight = activityHeight; viewfinderWidth = activityHeight * aspectRatioSource;
aspectRatioActivity ≤ aspectRatioSource
L'autre cas se produit lorsque l'activité est "plus étroite" ou "plus haute". Nous pouvons réutiliser l'exemple précédent, sauf que dans l'exemple suivant, vous faites pivoter l'appareil de 90 degrés, ce qui donne une activité de 9:16 et une source de 3:4.
aspectRatioActivity = 9/16 = 0.5625 aspectRatioSource = 3/4 = 0.75
Dans ce cas, vous devez ajuster la source et le viseur à l'activité comme suit :
Vous devez faire correspondre la largeur du viseur à celle de l'activité (par opposition à la hauteur dans le cas précédent), tout en rendant le format du viseur identique à celui de la source. Pseudo-code :
viewfinderWidth = activityWidth; viewfinderHeight = activityWidth / aspectRatioSource;
Écrêtages
AutoFitSurfaceView.kt (github) des exemples Camera2 remplace SurfaceView et gère les proportions non concordantes en utilisant une image qui est égale ou "juste plus grande" que l'activité dans les deux dimensions, puis en découpant le contenu qui dépasse. Cela est utile pour les applications qui souhaitent que l'aperçu couvre l'intégralité de l'activité ou remplisse complètement une vue de dimensions fixes, sans déformer l'image.
Avertissement
L'exemple précédent tente de maximiser l'espace à l'écran en rendant l'aperçu légèrement plus grand que l'activité afin qu'aucun espace ne soit laissé vide. Cela repose sur le fait que les parties qui dépassent sont coupées par la mise en page parente (ou ViewGroup) par défaut. Ce comportement est cohérent avec RelativeLayout et LinearLayout, mais PAS avec ConstraintLayout. Un ConstraintLayout peut redimensionner les vues enfants pour les faire tenir dans la mise en page, ce qui casserait l'effet "center-crop" prévu et entraînerait des aperçus étirés. Vous pouvez utiliser ce commit comme référence.
TextureView
TextureView offre un contrôle maximal sur le contenu de l'aperçu de la caméra, mais cela a un coût en termes de performances. Il faut également plus de travail pour que l'aperçu de la caméra s'affiche correctement.
Source
Sous TextureView, la plate-forme Android fait pivoter les tampons de sortie en fonction de l'orientation du capteur pour correspondre à l'orientation naturelle de l'appareil. Bien que TextureView gère l'orientation du capteur, il ne gère pas les rotations de l'écran. Il aligne les tampons de sortie sur l'orientation naturelle de l'appareil, ce qui signifie que vous devrez gérer vous-même les rotations de l'écran.
Cela est illustré dans le tableau suivant. Si vous essayez de faire pivoter les chiffres selon la rotation de l'écran correspondante, vous obtiendrez les mêmes chiffres dans SurfaceView.
| Rotation de l'écran | Téléphone (orientation naturelle = portrait) | Ordinateur portable (orientation naturelle = paysage) |
|---|---|---|
| 0 | ![]() |
![]() |
| 90 | ![]() |
![]() |
| 180 | ![]() |
![]() |
| 270 | ![]() |
![]() |
Mise en page
La mise en page est un peu délicate dans le cas de TextureView. Il a déjà été suggéré d'utiliser une matrice de transformation pour TextureView, mais cette méthode ne fonctionne pas pour tous les appareils. Nous vous conseillons plutôt de suivre les étapes décrites ici.
Voici la procédure en trois étapes pour mettre en page correctement les aperçus sur une TextureView :
- Définissez la taille de TextureView pour qu'elle soit identique à la taille d'aperçu choisie.
- Remettez à l'échelle la TextureView potentiellement étirée pour qu'elle retrouve les dimensions d'origine de l'aperçu.
-
Faites pivoter TextureView de
displayRotationdans le sens inverse des aiguilles d'une montre.
Supposons que vous ayez un téléphone dont l'écran est orienté à 90 degrés.
1. Définissez la taille de TextureView pour qu'elle soit identique à la taille de l'aperçu choisie.
Supposons que la taille de l'aperçu que vous avez choisie soit previewWidth × previewHeight, où previewWidth > previewHeight (la sortie du capteur est naturellement au format paysage). Lors de la configuration d'une session de capture, vous devez appeler SurfaceTexture#setDefaultBufferSize(int width, height) pour spécifier la taille de l'aperçu (previewWidth × previewHeight).
Avant d'appeler setDefaultBufferSize, il est important que vous définissiez également la taille de TextureView sur `previewWidth × previewHeight` avec View#setLayoutParams(android.view.ViewGroup.LayoutParams). En effet, TextureView appelle SurfaceTexture#setDefaultBufferSize(int width, height) avec sa largeur et sa hauteur mesurées. Si la taille de TextureView n'est pas définie explicitement au préalable, cela peut entraîner une condition de concurrence. Pour éviter cela, définissez explicitement la taille de TextureView en premier.
Il est possible que TextureView ne corresponde pas aux dimensions de la source. Dans le cas des téléphones, la source est au format portrait, mais la TextureView est au format paysage en raison des layoutParams que vous venez de définir. Les aperçus seraient alors étirés, comme illustré ci-dessous :
2. Remettre à l'échelle la TextureView potentiellement étirée pour qu'elle retrouve les dimensions d'origine de l'aperçu
Pour redimensionner l'aperçu étiré aux dimensions de la source, tenez compte des éléments suivants.
Les dimensions de la source (sourceWidth × sourceHeight) sont les suivantes :
-
previewHeight × previewWidth, si l'orientation naturelle est portrait ou portrait inversé (l'orientation du capteur est de 90 ou 270 degrés) -
previewWidth × previewHeight, si l'orientation naturelle est paysage ou paysage inversé (l'orientation du capteur est de 0 ou 180 degrés)
Correction de l'étirement à l'aide de View#setScaleX(float) et View#setScaleY(float)
-
setScaleX(
sourceWidth / previewWidth) -
setScaleY(
sourceHeight / previewHeight)
3. Faire pivoter l'aperçu de `displayRotation` dans le sens inverse des aiguilles d'une montre
Comme mentionné précédemment, vous devez faire pivoter l'aperçu de displayRotation dans le sens inverse des aiguilles d'une montre pour compenser la rotation de l'écran.
Pour ce faire, View#setRotation(float).
-
setRotation(
-displayRotation), car elle effectue une rotation dans le sens des aiguilles d'une montre.
Exemples
-
PreviewViewde camerax dans Jetpack gère la mise en page TextureView comme décrit précédemment. Il configure la transformation avec PreviewCorrector.
Remarque : Si vous avez déjà utilisé une matrice de transformation pour TextureView dans votre code, il est possible que l'aperçu ne s'affiche pas correctement sur un appareil naturellement en mode paysage, comme un Chromebook. Il est probable que votre matrice de transformation suppose à tort que l'orientation du capteur est de 90 ou 270 degrés. Vous pouvez vous référer à ce commit sur GitHub pour trouver une solution de contournement, mais nous vous recommandons vivement de migrer votre application pour utiliser la méthode décrite ici.





















