Frame Processors
What are frame processors?​
Frame processors are functions that are written in JavaScript (or TypeScript) which can be used to process frames the camera "sees". Inside those functions you can call Frame Processor Plugins, which are high performance native functions specifically designed for certain use-cases.
For example, you might want to create an object detector app without writing any native code, while still achieving native performance:
function App() {
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
const objects = detectObjects(frame)
console.log(`Detected ${objects.length} objects.`)
}, [])
return (
<Camera
{...cameraProps}
frameProcessor={frameProcessor}
/>
)
}
Frame processors are by far not limited to object detection, other examples include:
- ML for facial recognition
- Using Tensorflow, MLKit Vision, Apple Vision or other libraries
- Creating realtime video-chats using WebRTC to directly send the camera frames over the network
- Creating scanners for custom codes such as Snapchat's SnapCodes or Apple's AppClips
- Creating snapchat-like filters, e.g. draw a dog-mask filter over the user's face
- Creating color filters with depth-detection
- Drawing boxes, text, overlays, or colors on the screen in realtime
- Rendering filters and shaders such as Blur, inverted colors, beauty filter, or more on the screen
Because they are written in JS, Frame Processors are simple, powerful, extensible and easy to create while still running at native performance. (Frame Processors can run up to 1000 times a second!) Also, you can use fast-refresh to quickly see changes while developing or publish over-the-air updates to tweak the object detector's sensitivity in live apps without pushing a native update.
react-native-worklets-core​
Frame Processors require react-native-worklets-core 0.2.0 or higher. Install it:
npm i react-native-worklets-core
And add the plugin to your babel.config.js
:
module.exports = {
plugins: [
['react-native-worklets-core/plugin'],
],
}
The Frame
​
A Frame Processor is called for every Camera frame, and exposes information about the frame in the Frame
parameter.
The Frame
parameter wraps the native GPU-based frame buffer in a C++ HostObject (a ~1.5MB buffer at 4k), and allows you to access information such as it's resolution or pixel format directly from JS:
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
console.log(`Frame: ${frame.width}x${frame.height} (${frame.pixelFormat})`)
}, [])
Additionally, you can also directly access the Frame's pixel data using toArrayBuffer()
:
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
if (frame.pixelFormat === 'rgb') {
const buffer = frame.toArrayBuffer()
const data = new Uint8Array(buffer)
console.log(`Pixel at 0,0: RGB(${data[0]}, ${data[1]}, ${data[2]})`)
}
}, [])
It is however recommended to use native Frame Processor Plugins for processing, as those are much faster than JavaScript and can sometimes operate with the GPU buffer directly.
You can simply pass a Frame
to a native Frame Processor Plugin directly.
Interacting with Frame Processors​
Access JS values​
Since Frame Processors run in Worklets, you can directly use JS values such as React state which are readonly-copied into the Frame Processor:
// User can look for specific objects
const targetObject = 'banana'
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
const objects = detectObjects(frame)
const bananas = objects.filter((o) => o.type === targetObject)
console.log(`Detected ${bananas} bananas!`)
}, [targetObject])
Shared Values​
You can also easily read from, and assign to Shared Values, which can be written to from inside a Frame Processor and read from any other context (either React JS, Skia, or Reanimated):
const bananas = useSharedValue([])
// Detect Bananas in Frame Processor
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
const objects = detectObjects(frame)
bananas.value = objects.filter((o) => o.type === 'banana')
}, [bananas])
// Draw bananas in a Skia Canvas
const onDraw = useDrawCallback((canvas) => {
for (const banana of bananas.value) {
const rect = Skia.XYWHRect(banana.x,
banana.y,
banana.width,
banana.height)
const paint = Skia.Paint()
paint.setColor(Skia.Color('red'))
frame.drawRect(rect, paint)
}
})
Call functions​
And you can also call back to the React-JS thread by using createRunInJsFn(...)
:
const onFaceDetected = Worklets.createRunInJsFn((face: Face) => {
navigation.push("FiltersPage", { face: face })
})
const frameProcessor = useFrameProcessor((frame) => {
'worklet'
const faces = scanFaces(frame)
if (faces.length > 0) {
onFaceDetected(faces[0])
}
}, [onFaceDetected])