captureFrame method
Captures a single RGB frame from the camera.
While a preview is running (see startPreview) this returns the latest frame from the native frame cache without disturbing the video stream.
On Android the returned frame is rotated to match the on-screen preview (the display pipeline already orients the preview), so coordinates derived from the frame map 1:1 onto the displayed image.
Implementation
@override
Future<Map<String, dynamic>> captureFrame({int byteOrder = 2}) async {
final element = _videoElement;
if (element == null) {
throw PlatformException(
code: 'CAPTURE_FAILED', message: 'No frame available.');
}
// Wait for frame metadata the first time so width/height are non-zero.
if (element.videoWidth == 0) {
final completer = Completer<void>();
// The same JS function object must be used for add and remove, so the
// closure captures the converted callback through a late variable.
late final JSFunction metadataCallback;
metadataCallback = ((dom.Event _) {
element.removeEventListener('loadedmetadata', metadataCallback);
if (!completer.isCompleted) completer.complete();
}).toJS;
element.addEventListener('loadedmetadata', metadataCallback);
await completer.future
.timeout(const Duration(seconds: 2), onTimeout: () {});
}
final width = element.videoWidth;
final height = element.videoHeight;
if (width == 0 || height == 0) {
throw PlatformException(
code: 'CAPTURE_FAILED', message: 'No frame available.');
}
final canvas = dom.HTMLCanvasElement()
..width = width
..height = height;
final context = canvas.context2D;
context.drawImage(element, 0, 0, width.toDouble(), height.toDouble());
final rgba = context.getImageData(0, 0, width, height).data.toDart;
final rgb = Uint8List(width * height * 3);
for (var src = 0, dst = 0; dst < rgb.length; src += 4, dst += 3) {
rgb[dst] = rgba[src];
rgb[dst + 1] = rgba[src + 1];
rgb[dst + 2] = rgba[src + 2];
}
return <String, dynamic>{'width': width, 'height': height, 'data': rgb};
}