AccueilClientsExpertisesBlogOpen SourceJobsContact

7 mai 2024

React Native Camera Vision x Skia : les nouveautés de la v4

5 minutes de lecture

React Native Camera Vision x Skia : les nouveautés de la v4
🇺🇸 This post is also available in english

Si vous avez déjà développé une application mobile nécessitant l’usage de la caméra, alors le nom de React Native Vision Camera vous dit sûrement quelque chose. Flexible et efficace, cette librairie permet d’intégrer facilement les fonctionnalités vidéo et photo aux applications react native.

La v4 de la librairie annonce le grand retour du hook useSkiaFrameProcessor, qui promettait une intégration des fonctionnalités de la bibliothèque graphique Skia directement aux frames de la caméra. Un temps considéré sur la v3, ce hook avait finalement été retiré en raison de problèmes de maintenabilité. Mais la détermination de Marc Rousavy le créateur de React Native Vision Camera, a finalement porté ses fruits, et c’est cette dernière version qui accueille le nouveau hook.

Chez Premier Octet, nous travaillons depuis un certain temps sur notre nouveau projet mobile : Photobooth AI, un studio photo s’appuyant sur l'intelligence artificielle pour réinventer vos portraits. Cette application Expo utilise React Native Vision Camera pour la gestion de la capture d'images. C’est donc l’occasion pour nous d’explorer de nouvelles fonctionnalités pour notre application.

Photobooth AI

Configuration de l'application sur Expo

Avant toute chose, nous devons bien sûr installer React Native Vision Camera

npx expo install react-native-vision-camera

(par ici pour la doc détaillée)

Pour gérer les actions au sein de notre frame processor, nous aurons également besoin de React Native Worklets Core, une librairie de gestion des worklet créée par l'agence de Marc Rousavy

yarn add react-native-worklets-core

N'oubliez pas d'ajouter le plugin à votre babel.config.js

module.exports = {
  plugins: [['react-native-worklets-core/plugin']],
}

Et bien entendu React Native Skia pour la partie dessin

yarn add @shopify/react-native-skia
cd ios
pod install

Dessiner sur nos frames avec useSkiaFrameProcessor

Depuis la v3 ​​de la bibliothèque, la fonctionnalité frameProcessor permet d'appliquer un worklet JavaScript à chaque image capturée par la caméra. Comme les frameProcessor traitent chaque frame de manière synchrone, la vitesse d'exécution de leur code est essentielle pour avoir un affichage fluide. Grâce aux worklets, les opérations de nos frameProcessors sont exécutées sur un thread secondaire, ce qui évite de trop impacter les performances de l'application et de viser les 60 FPS.

Le nouveau hook Skia rajoute une surcouche à ce fonctionnement en intégrant directement un canvas à nos frames. On peut tracer ce que l’on souhaite en utilisant l’API impérative de React Native Skia sur notre frame :

CameraScreen.tsx
import { PaintStyle, Skia } from "@shopify/react-native-skia"
import {
  Camera,
  useSkiaFrameProcessor,
} from "react-native-vision-camera"

[...]

// On initialise une couleur
const paint = Skia.Paint()
paint.setColor(Skia.Color("red"))

// On définit notre frameProcessor
const frameProcessor = useSkiaFrameProcessor((frame) => {
   "worklet"
   frame.render() // nécessaire pour voir le rendu caméra
   const centerX = frame.width / 2
   const centerY = frame.height / 2
   frame.drawCircle(centerX, centerY, 50, paint)
}, [])

[...]

// On passe le frame processor à notre caméra
<Camera
ref={camera}
device={device}
format={format}
orientation="portrait"
photo
frameProcessor={frameProcessor}
/>

Les coordonnées sont exprimées sur un axe x,y par rapport à l’origine du frame en haut à gauche (0,0).

Et notre forme apparaît sur notre écran de photobooth : Dessiner des formes sur les frames avec Skia

Actuellement, les données affichées sur le frame sont visibles uniquement en mode de prévisualisation. Par conséquent, la surcouche Skia ne sera pas intégrée aux vidéos et aux photos capturées par React Native Vision Camera.

Ajouter des animations avec Reanimated

Profitons du nouveau hook Skia pour afficher un guide de positionnement du visage à nos utilisateurs. Pour le rendre un peu plus ludique, une animation serait plus sympathique pour afficher la forme. Skia propose une intégration avec Reanimated pour gérer les animations, installons la librairie sur le projet :

npx expo install react-native-reanimated

Pour obtenir une animation d’apparition progressive du contour avec le temps, nous initialisons une sharedValue et y appliquons le hook withTiming de Reanimated. Afin de s’assurer que notre caméra soit activée avant le début de l'animation, nous décalerons légèrement son démarrage avec le hook withDelay.

import { Worklets, useSharedValue as useWorkletShareValue } from 'react-native-worklets-core'
import { PaintStyle, Skia } from '@shopify/react-native-skia'
import { withTiming, useSharedValue, useAnimatedReaction, withDelay } from 'react-native-reanimated'

const paint = Skia.Paint()
paint.setColor(Skia.Color('#F1EBE1'))

const reanimatedProgress = useSharedValue(0)
useEffect(() => {
  reanimatedProgress.value = withDelay(
    500,
    withTiming(1, {
      duration: 2000,
    })
  )
}, [])

Une petite subtilité pour accéder à notre valeur de progression dans le worklet

La valeur définie par useSharedValue de reanimated n’est malheureusement pas accessible depuis le frameProcessor. React Native Worklets Core dispose de son propre hook useSharedValue pour accéder à des valeurs au sein du frameProcessor. En attendant une meilleure compatibilité entre les deux (peut-être dans la v5 🤞) un passage de l’une à l’autre des variables est possible en utilisant useAnimatedReaction pour updater la seconde sharedValueavec les valeurs de la première.

const workletProgress = useWorkletShareValue(0)
useAnimatedReaction(
  () => reanimatedProgress.value,
  (value, prevValue) => {
    workletProgress.value = value
  }
)

Nous passons ensuite le paramètre de progression de dessin dans notre frameProcessor pour afficher le guide graduellement :

const frameProcessor = useSkiaFrameProcessor(
  (frame) => {
    'worklet'
    frame.render()

    const width = 340
    const height = 450
    const x = frame.width / 2 - width / 2
    const y = frame.height / 2 - height / 2
    const path = Skia.Path.Make()

    path.addOval(Skia.XYWHRect(x, y, width, height))
    path.trim(0, workletProgress.value, false)

    paint.setStyle(PaintStyle.Stroke)
    paint.setStrokeWidth(4)
    frame.drawPath(path, paint)
  },
  [paint, workletProgress]
)

Et l’animation se lance bien au démarrage de la caméra 🥳

Plus de stabilité sur Android et la géolocalisation en prime

Enfin cette nouvelle version corrige quelques bugs gênants que nous avions pu expérimenter sur Android (retournement aléatoire de photos et problème d'activation de la caméra au lancement). Initialement basée sur le package Camera2 la v4 utilise dorénavant la lib Android CameraX plus fiable. React Native Vision Camera nous promet donc plus de stabilité avec cette nouvelle version et c’est un soulagement 🥲 !
On notera aussi l'ajout, de la possibilité de lier des tags de localisation sur les photos et vidéos, ainsi que d'un nouveau hook useLocationPermission pour obtenir les permissions liées.

Conclusion

Avec l'introduction de ce nouveau hook Skia, React Native Vision Camera ouvre de nouvelles perspectives passionnantes pour les animations et les interactions avec la caméra. Bien que la documentation de React Native Skia ne soit pas très détaillée concernant l'API impérative, il est assez facile de trouver des moyens d'atteindre ses objectifs, et nous espérons que ce hook marquera le début d'une collaboration fructueuse entre les deux librairies.

La possibilité d'intégrer des animations avec Reanimated, même si le partage des shared values reste perfectible, rend la fonctionnalité particulièrement attrayante. Nous sommes donc impatients de découvrir les prochaines versions et améliorations à venir, mais cette v4 annonce d’ores et déjà un futur prometteur pour React Native Vision Camera 👏👏👏 !

18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub
Twitter
Flux RSS

Naviguez à vue