@data_is_null/face-map-expo
v0.1.2
Published
Expo module for detecting facial landmarks and rendering animated face mesh overlays
Maintainers
Readme
face-map-expo
Expo native module that detects facial landmarks on static images and renders animated face mesh overlays.
- iOS — Apple Vision framework (
VNDetectFaceLandmarksRequest) - Android — Google ML Kit Face Detection (
com.google.mlkit:face-detection)
Installation
npx expo install @data_is_null/face-map-expo @shopify/react-native-skia react-native-reanimated react-native-workletsThis module uses native code. It does not work in Expo Go — you need a development build:
npx expo prebuild
npx expo run:ios # or run:androidiOS
No additional setup. Uses the built-in Vision framework. Minimum deployment target: iOS 15.1.
Android
ML Kit is bundled automatically via the module's build.gradle. No manual dependency setup needed.
API Reference
detectFaceLandmarks(imageUri: string): Promise<FaceMeshResult>
Detects facial landmarks in an image. Returns normalized coordinates (0..1).
Parameters:
imageUri— Local file URI (file://...), content URI (Android), or remote HTTP(S) URL.
Returns:
type FacePoint = { x: number; y: number };
type FaceRegion =
| 'faceContour' | 'leftEye' | 'rightEye'
| 'leftEyebrow' | 'rightEyebrow' | 'nose'
| 'outerLips' | 'innerLips' | 'leftPupil' | 'rightPupil';
type FaceMeshResult = {
points: FacePoint[]; // flat array of all detected points
regions: Record<FaceRegion, FacePoint[]>; // points grouped by facial region
bounds: { // normalized face bounding box
x: number;
y: number;
width: number;
height: number;
};
imageWidth: number;
imageHeight: number;
};Error codes (Promise rejection):
| Code | Meaning |
|------|---------|
| ERR_INVALID_IMAGE | Could not load or decode the image |
| ERR_NO_FACE | No face detected in the image |
| ERR_DETECTION_FAILED | Detection engine error |
useFaceMesh(imageUri: string | undefined)
React hook wrapper. Re-runs detection when imageUri changes.
const { result, loading, error } = useFaceMesh(imageUri);<FaceMeshOverlay />
Renders an animated face mesh overlay using @shopify/react-native-skia and react-native-reanimated.
<FaceMeshOverlay
imageUri={uri}
width={300}
height={400}
animationStyle="radialPulse"
dotColor="#4A9EFF"
dotSize={2}
lineColor="rgba(74, 158, 255, 0.5)"
showLines
/>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| imageUri | string | required | Image URI to detect faces in |
| width | number | required | Overlay width in pixels |
| height | number | required | Overlay height in pixels |
| animationStyle | AnimationStyle | 'sequentialScan' | Animation pattern |
| duration | number | 2500 | Animation cycle duration (ms) |
| dotColor | string | '#4A9EFF' | Landmark dot color |
| dotSize | number | 2 | Landmark dot radius |
| lineColor | string | 'rgba(74, 158, 255, 0.5)' | Connecting line color |
| showLines | boolean | true | Draw lines between region points |
Animation styles:
| Style | Description |
|-------|-------------|
| sequentialScan | Top-to-bottom sweep |
| radialPulse | Outward from center |
| randomReveal | Dots pop in randomly |
| progressFill | Left-to-right fill |
Each dot appears with a scale-up animation and a soft glow halo. Lines between region points fade in smoothly. Eye and lip regions draw as closed paths.
Platform Differences
Landmark Density
| | iOS (Vision) | Android (ML Kit) |
|---|---|---|
| Total points | ~70–80 | ~130+ |
| Face contour | Variable-length | 36 fixed points |
| Eyes | Variable-length | 16 points each |
| Eyebrows | Single region | Combined from top + bottom contours |
| Nose | Single region | Combined from bridge + bottom contours |
| Lips | outerLips / innerLips | Combined from upper/lower top/bottom |
| Pupils | Dedicated region | Single landmark point each |
iOS Vision also detects noseCrest and medianLine regions which are not included in the cross-platform output.
Coordinate System
Both platforms normalize coordinates to 0..1 relative to image dimensions. iOS Vision uses bottom-left origin internally (converted to top-left by the module). Android ML Kit uses top-left origin natively.
Camera Capture
On iOS, images captured via expo-camera CameraView (takePictureAsync) produce view snapshots at screen resolution. The module includes a fallback that re-renders these into standard bitmaps for reliable detection. Photos from the system camera (ImagePicker.launchCameraAsync) or photo library work without fallback.
Usage Example
import { useState } from 'react';
import { Image, View, Button, Dimensions } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { FaceMeshOverlay } from 'face-map-expo';
const W = Dimensions.get('window').width;
export default function FaceScreen() {
const [uri, setUri] = useState<string>();
const [size, setSize] = useState({ w: W, h: W });
const pick = async () => {
const r = await ImagePicker.launchCameraAsync({ mediaTypes: ['images'], quality: 1 });
if (!r.canceled) {
const a = r.assets[0];
setUri(a.uri);
setSize({ w: W, h: W * (a.height / a.width) });
}
};
return (
<View style={{ flex: 1 }}>
<Button title="Take Photo" onPress={pick} />
{uri && (
<View style={{ width: size.w, height: size.h }}>
<Image source={{ uri }} style={{ width: size.w, height: size.h }} />
<FaceMeshOverlay
imageUri={uri}
width={size.w}
height={size.h}
animationStyle="radialPulse"
showLines
/>
</View>
)}
</View>
);
}Permissions
The module itself requires no permissions. The example app uses expo-camera and expo-image-picker:
| Platform | Permission | Purpose |
|----------|-----------|---------|
| iOS | NSCameraUsageDescription | Camera access |
| iOS | NSPhotoLibraryUsageDescription | Photo library access |
| Android | CAMERA | Camera access |
| Android | READ_MEDIA_IMAGES (API 33+) | Photo library access |
Development
# Install dependencies
npm install
cd example && npm install
# Run example app
cd example
npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:androidLicense
MIT
