Bonjour ! Bienvenue dans notre série d'articles sur CameraX et Jetpack Compose. Dans les articles précédents, nous avons abordé les principes de base de la configuration d'un aperçu de la caméra et ajouté une fonctionnalité de mise au point par appui.
🧱 Partie 1: Créer un aperçu de caméra de base à l'aide du nouvel artefact camera-compose. Nous avons abordé la gestion des autorisations et l'intégration de base.
👆 Partie 2: Utiliser le système de gestes, les graphiques et les coroutines Compose pour implémenter une mise au point par appui visuelle.
🔦 Partie 3 (cet article) : Découvrir comment superposer des éléments d'interface utilisateur Compose sur l'aperçu de votre caméra pour une expérience utilisateur plus riche.
📂 Partie 4 : Utiliser des API adaptatives et le framework d'animation Compose pour animer en douceur le passage au mode sur table et inversement sur les téléphones pliables.
Dans cet article, nous allons aborder un sujet un peu plus attrayant visuellement : l'implémentation d'un effet de surbrillance sur l'aperçu de notre caméra, en utilisant la détection de visages comme base de l'effet. Pourquoi, me direz-vous ? Je ne sais pas. Mais c'est plutôt cool 🙂. Et, plus important encore, cela montre comment nous pouvons facilement traduire les coordonnées du capteur en coordonnées d'interface utilisateur, ce qui nous permet de les utiliser dans Compose !
Activer la détection de visages
Commençons par modifier CameraPreviewViewModel pour activer la détection de visages. Nous allons utiliser l'API Camera2Interop, qui nous permet d'interagir avec l'API Camera2 sous-jacente à partir de CameraX. Cela nous donne la possibilité d'utiliser des fonctionnalités de caméra qui ne sont pas exposées directement par CameraX. Nous devons apporter les modifications suivantes :
- Créer un StateFlow qui contient les limites du visage sous forme de liste de
Rect. - Définir l'option de requête de capture
STATISTICS_FACE_DETECT_MODEsur FULL, ce qui active la détection de visages. - Définir un
CaptureCallbackpour obtenir les informations sur le visage à partir du résultat de la capture.
class CameraPreviewViewModel : ViewModel() { ... private val _sensorFaceRects = MutableStateFlow(listOf<Rect>()) val sensorFaceRects: StateFlow<List<Rect>> = _sensorFaceRects.asStateFlow() private val cameraPreviewUseCase = Preview.Builder() .apply { Camera2Interop.Extender(this) .setCaptureRequestOption( CaptureRequest.STATISTICS_FACE_DETECT_MODE, CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL ) .setSessionCaptureCallback(object : CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) result.get(CaptureResult.STATISTICS_FACES) ?.map { face -> face.bounds.toComposeRect() } ?.toList() ?.let { faces -> _sensorFaceRects.update { faces } } } }) } .build().apply { ... }
Une fois ces modifications apportées, notre modèle de vue émet une liste d'objets Rect représentant les cadres de délimitation des visages détectés dans les coordonnées du capteur.
Traduire les coordonnées du capteur en coordonnées d'interface utilisateur
Les cadres de délimitation des visages détectés que nous avons stockés dans la section précédente utilisent des coordonnées dans le système de coordonnées du capteur. Pour dessiner les cadres de délimitation dans notre interface utilisateur, nous devons transformer ces coordonnées afin qu'elles soient correctes dans le système de coordonnées Compose. Nous devons procéder comme suit :
- Transformer les coordonnées du capteur en coordonnées du tampon d'aperçu
- Transformer les coordonnées du tampon d'aperçu en coordonnées de l'interface utilisateur Compose
Ces transformations sont effectuées à l'aide de matrices de transformation. Chacune des transformations possède sa propre matrice :
- Notre
SurfaceRequestcontient une instanceTransformationInfo, qui contient une matricesensorToBufferTranform. - Notre
CameraXViewfinderest associé à unCoordinateTransformer. Vous vous souvenez peut-être que nous avons déjà utilisé ce transformateur dans l'article de blog précédent pour transformer les coordonnées de mise au point par appui.
Nous pouvons créer une méthode d'assistance qui peut effectuer la transformation pour nous :
private fun List<Rect>.transformToUiCoords( transformationInfo: SurfaceRequest.TransformationInfo?, uiToBufferCoordinateTransformer: MutableCoordinateTransformer ): List<Rect> = this.map { sensorRect -> val bufferToUiTransformMatrix = Matrix().apply { setFrom(uiToBufferCoordinateTransformer.transformMatrix) invert() } val sensorToBufferTransformMatrix = Matrix().apply { transformationInfo?.let { setFrom(it.sensorToBufferTransform) } } val bufferRect = sensorToBufferTransformMatrix.map(sensorRect) val uiRect = bufferToUiTransformMatrix.map(bufferRect) uiRect }
- Nous parcourons la liste des visages détectés et, pour chaque visage, nous exécutons la transformation.
- Le
CoordinateTransformer.transformMatrixque nous obtenons de notreCameraXViewfindertransforme par défaut les coordonnées de l'interface utilisateur en coordonnées du tampon. Dans notre cas, nous voulons que la matrice fonctionne dans l'autre sens, en transformant les coordonnées du tampon en coordonnées de l'interface utilisateur. Par conséquent, nous utilisons la méthodeinvert()pour inverser la matrice. - Nous transformons d'abord le visage des coordonnées du capteur en coordonnées du tampon à l'aide de
sensorToBufferTransformMatrix, puis nous transformons ces coordonnées du tampon en coordonnées de l'interface utilisateur à l'aide debufferToUiTransformMatrix.
Implémenter l'effet de surbrillance
Nous allons maintenant mettre à jour le composable CameraPreviewContent pour dessiner l'effet de surbrillance. Nous allons utiliser un Canvas composable pour dessiner un masque de dégradé sur l'aperçu, ce qui rendra les visages détectés visibles :
@Composable fun CameraPreviewContent( viewModel: CameraPreviewViewModel, modifier: Modifier = Modifier, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current ) { val surfaceRequest by viewModel.surfaceRequest.collectAsStateWithLifecycle() val sensorFaceRects by viewModel.sensorFaceRects.collectAsStateWithLifecycle() val transformationInfo by produceState<SurfaceRequest.TransformationInfo?>(null, surfaceRequest) { try { surfaceRequest?.setTransformationInfoListener(Runnable::run) { transformationInfo -> value = transformationInfo } awaitCancellation() } finally { surfaceRequest?.clearTransformationInfoListener() } } val shouldSpotlightFaces by remember { derivedStateOf { sensorFaceRects.isNotEmpty() && transformationInfo != null} } val spotlightColor = Color(0xDDE60991) .. surfaceRequest?.let { request -> val coordinateTransformer = remember { MutableCoordinateTransformer() } CameraXViewfinder( surfaceRequest = request, coordinateTransformer = coordinateTransformer, modifier = .. ) AnimatedVisibility(shouldSpotlightFaces, enter = fadeIn(), exit = fadeOut()) { Canvas(Modifier.fillMaxSize()) { val uiFaceRects = sensorFaceRects.transformToUiCoords( transformationInfo = transformationInfo, uiToBufferCoordinateTransformer = coordinateTransformer ) // Fill the whole space with the color drawRect(spotlightColor) // Then extract each face and make it transparent uiFaceRects.forEach { faceRect -> drawRect( Brush.radialGradient( 0.4f to Color.Black, 1f to Color.Transparent, center = faceRect.center, radius = faceRect.minDimension * 2f, ), blendMode = BlendMode.DstOut ) } } } } }
Voici comment cela fonctionne :
- Nous collectons la liste des visages à partir du modèle de vue.
- Pour nous assurer que nous ne recomposons pas l'intégralité de l'écran chaque fois que la liste des visages détectés change, nous utilisons
derivedStateOfpour savoir si des visages sont détectés. Nous pouvons ensuite utiliserAnimatedVisibilitypour animer la superposition colorée. - Le
surfaceRequestcontient les informations dont nous avons besoin pour transformer les coordonnées du capteur en coordonnées du tampon dans leSurfaceRequest.TransformationInfo. Nous utilisons la fonctionproduceStatepour configurer un écouteur dans la requête de surface et effacer cet écouteur lorsque le composable quitte l'arborescence de composition. - Nous utilisons un
Canvaspour dessiner un rectangle rose translucide qui couvre l'intégralité de l'écran. - Nous différons la lecture de la variable
sensorFaceRectsjusqu'à ce que nous nous trouvions dans le bloc de dessinCanvas. Nous transformons ensuite les coordonnées en coordonnées de l'interface utilisateur. - Nous parcourons les visages détectés et, pour chaque visage, nous dessinons un dégradé radial qui rendra l'intérieur du rectangle du visage transparent.
- Nous utilisons
BlendMode.DstOutpour nous assurer que nous découpons le dégradé du rectangle rose, ce qui crée l'effet de surbrillance.
Remarque : Lorsque vous remplacez la caméra par DEFAULT_FRONT_CAMERA vous remarquerez que le projecteur est mis en miroir ! Il s'agit d'un problème connu, suivi dans l' outil de suivi des problèmes de Google.
Résultat
Avec ce code, nous disposons d'un effet de surbrillance entièrement fonctionnel qui met en évidence les visages détectés. Vous trouverez l'extrait de code complet ici.
Cet effet n'est que le début. En utilisant la puissance de Compose, vous pouvez créer une myriade d'expériences de caméra visuellement époustouflantes. La possibilité de transformer les coordonnées du capteur et du tampon en coordonnées de l'interface utilisateur Compose et inversement signifie que nous pouvons utiliser toutes les fonctionnalités de l'interface utilisateur Compose et les intégrer de manière transparente au système de caméra sous-jacent. Avec les animations, les graphiques d'interface utilisateur avancés, la gestion simple de l'état de l'interface utilisateur et le contrôle total des gestes, votre imagination est la seule limite !
Dans le dernier article de la série, nous verrons comment utiliser des API adaptatives et le framework d'animation Compose pour passer de manière transparente d'une interface utilisateur de caméra à une autre sur les appareils pliables. Tenez-vous informé !
Les extraits de code de ce blog sont soumis à la licence suivante :
// Copyright 2024 Google LLC. SPDX-License-Identifier: Apache-2.0
Merci beaucoup à Nick Butcher, Alex Vanyo, Trevor McGuire, Don Turner et Lauren Ward pour leur examen et leurs commentaires. Merci à Yasith Vidanaarachch pour son travail acharné.
-
TutorielsDans cet article, vous apprendrez à utiliser l'API de test waitUntil dans Compose pour attendre que certaines conditions soient remplies.
Jose Alcérreca • Temps de lecture : 3 min -
TutorielsBien que les performances des applications soient souvent associées à une interface utilisateur fluide et à des temps de démarrage rapides, la mémoire constitue la base silencieuse sur laquelle ces métriques visibles sont construites. Ce n'est un secret pour personne : la mémoire des appareils est plus importante que jamais.
Alice Yuan, Ajesh Pai, Fung Lam • Temps de lecture : 10 min -
TutorielsNous sommes ravis d'annoncer aujourd'hui la sortie d'une nouvelle identité d'e-mail validée émise par Google, que les développeurs peuvent désormais récupérer directement à partir de l'API d'identité numérique Credential Manager d'Android.
Niharika Arora, Jean-Pierre Pralle • Temps de lecture : 3 min
Recevez chaque semaine les dernières informations sur le développement Android dans votre boîte de réception.