Camera frames
Drawing VisionCamera frames through tgfx.
npm install react-native-vision-camera react-native-vision-camera-workletsuseCameraFrame turns a VisionCamera frame into
a TgfxImage and a
CameraFramePosture, together a
CameraFrameSource.
import { useCamera, useFrameOutput } from 'react-native-vision-camera'
import { Canvas, CameraFrame, useCameraFrame } from 'react-native-tgfx'
export function Preview() {
const { image, posture, onFrame } = useCameraFrame()
const frameOutput = useFrameOutput({
pixelFormat: 'yuv',
dropFramesWhileBusy: true,
enablePhysicalBufferRotation: true,
// Headless: this output is the only one, so deferring it behind a preview starts it late.
allowDeferredStart: false,
onFrame,
})
useCamera({
isActive: true,
device: 'back',
outputs: [frameOutput],
})
return (
<Canvas style={StyleSheet.absoluteFill} opaque={true}>
<CameraFrame image={image} {...posture} />
</Canvas>
)
}Get camera permission first. No camera view is mounted. Keep
allowDeferredStart: false, or frames start late.
Keep useFrameOutput options stable (a module constant or memoised).
CameraFrameProps · CameraFrameOptions
Zero copy
imageFromNativeBuffer imports the
frame's buffer (NativeBufferLike) as a
texture with no copy. Later frames reuse the handle:
handle.value == null
? imageFromNativeBuffer(buffer.pointer, format) // first frame
: handle.value.setNativeBuffer(buffer.pointer, format) // every frame aftersetNativeBuffer releases the previous buffer
before returning.
Don't write into a buffer a handle points at.
useCameraFrame manages this for you.
The frame in your callback dies when it returns. Don't retain it, call
getNativeBuffer() again, or store its pointer.
No JS per frame
npm install react-native-tgfx-vision-camerauseTgfxCameraSink does the import natively, so frames skip JS. Attach it once
the handle exists:
const sink = useTgfxCameraSink()
useEffect(() => {
if (image != null) sink.attach(frameOutput, image.handle)
}, [sink, frameOutput, image])After attach, orientation stops reaching JS, so keep
enablePhysicalBufferRotation: true. Not for frame processors that run JS,
like a detector.
Zooming
The viewport doesn't move a <CameraFrame>.
Wrap it in <Group transform> to zoom it.
Shading a frame
<CameraFrame image={image} {...posture}>
<Fill>
<Shader source={NEON}>
<ImageShader image={image} />
</Shader>
</Fill>
</CameraFrame>Keep tile modes at 'clamp', or YUV frames turn transparent.
Face blur
Allocate fixed slots. Zero-size slots draw nothing.
const SLOTS = [0, 1, 2, 3]
const [ovals] = useState(() => SLOTS.map(() => createHandle()))
const detect = useCallback((frame: Frame) => {
'worklet'
const faces = detector.detectFaces(frame)
for (const i of SLOTS) {
const b = faces[i]?.bounds
ovals[i]!.set({
x: b?.x ?? 0,
y: b?.y ?? 0,
width: b?.width ?? 0,
height: b?.height ?? 0,
})
}
}, [detector, ovals])
return { ...useCameraFrame(detect), ovals }<CameraFrame image={image} {...posture}>
{SLOTS.map((i) => (
<Oval key={i} ref={ovals[i]} x={0} y={0} width={0} height={0}>
<Paint>
<ImageShader image={image} />
<Blur blur={25} />
</Paint>
</Oval>
))}
</CameraFrame>