@@ -357,29 +357,126 @@ class _CameraViewState extends State<CameraView> {
357357
358358 // get image format
359359 final format = InputImageFormatValue .fromRawValue (image.format.raw);
360- // validate format depending on platform
361- // only supported formats:
362- // * nv21 for Android
363- // * bgra8888 for iOS
364- if (format == null ||
365- (Platform .isAndroid && format != InputImageFormat .nv21) ||
360+ if (format == null ) {
361+ print ('could not find format from raw value: ${image .format .raw }' );
362+ return null ;
363+ }
364+ // Validate format depending on platform
365+ const androidSupportedFormats = [
366+ InputImageFormat .nv21,
367+ InputImageFormat .yv12,
368+ InputImageFormat .yuv_420_888
369+ ];
370+
371+ if ((Platform .isAndroid && ! androidSupportedFormats.contains (format)) ||
366372 (Platform .isIOS && format != InputImageFormat .bgra8888)) {
373+ print ('image format is not supported: $format ' );
367374 return null ;
368375 }
369376
370- // since format is constraint to nv21 or bgra8888, both only have one plane
371- if (image.planes.length != 1 ) return null ;
372- final plane = image.planes.first;
377+ InputImageFormat resolvedFormat = format;
378+ final Uint8List bytes;
379+
380+ if (image.planes.length == 1 ) {
381+ bytes = image.planes.first.bytes;
382+ } else if (Platform .isAndroid &&
383+ (format == InputImageFormat .yuv_420_888 ||
384+ format == InputImageFormat .yv12) &&
385+ image.planes.length == 3 ) {
386+ bytes = _convertYUV420ToNV21 (image);
387+ resolvedFormat = InputImageFormat .nv21;
388+ } else {
389+ bytes = _concatenatePlanes (image);
390+ }
373391
374- // compose InputImage using bytes
375392 return InputImage .fromBytes (
376- bytes: plane. bytes,
393+ bytes: bytes,
377394 metadata: InputImageMetadata (
378395 size: Size (image.width.toDouble (), image.height.toDouble ()),
379- rotation: rotation, // used only in Android
380- format: format, // used only in iOS
381- bytesPerRow: plane. bytesPerRow, // used only in iOS
396+ rotation: rotation,
397+ format: resolvedFormat,
398+ bytesPerRow: image.planes.first. bytesPerRow,
382399 ),
383400 );
384401 }
402+
403+ // Reusable buffer to avoid per-frame allocations when concatenating planes.
404+ Uint8List ? _reusablePlaneBuffer;
405+
406+ Uint8List _concatenatePlanes (CameraImage image) {
407+ // Calculate the total number of bytes across all planes.
408+ final int totalBytes = image.planes.fold <int >(
409+ 0 ,
410+ (int sum, Plane plane) => sum + plane.bytes.length,
411+ );
412+
413+ // Ensure the reusable buffer is allocated and large enough.
414+ var buffer = _reusablePlaneBuffer;
415+ if (buffer == null || buffer.length < totalBytes) {
416+ buffer = Uint8List (totalBytes);
417+ _reusablePlaneBuffer = buffer;
418+ }
419+
420+ // Copy each plane's bytes into the reusable buffer.
421+ var offset = 0 ;
422+ for (final Plane plane in image.planes) {
423+ final bytes = plane.bytes;
424+ buffer.setRange (offset, offset + bytes.length, bytes);
425+ offset += bytes.length;
426+ }
427+
428+ // Return the reusable buffer directly when sizes match, or a zero-copy view otherwise.
429+ if (totalBytes == buffer.length) {
430+ return buffer;
431+ }
432+ return Uint8List .sublistView (buffer, 0 , totalBytes);
433+ }
434+
435+ Uint8List ? _reusableNv21Buffer;
436+ int _lastNv21Size = 0 ;
437+ Uint8List _convertYUV420ToNV21 (CameraImage image) {
438+ final int width = image.width;
439+ final int height = image.height;
440+ final int ySize = width * height;
441+ final int uvSize = ySize ~ / 2 ;
442+ final int requiredSize = ySize + uvSize;
443+
444+ if (_reusableNv21Buffer == null || _lastNv21Size != requiredSize) {
445+ _reusableNv21Buffer = Uint8List (requiredSize);
446+ _lastNv21Size = requiredSize;
447+ }
448+
449+ final Uint8List nv21 = _reusableNv21Buffer! ;
450+
451+ // Copy Y plane (strip row padding)
452+ final Plane yPlane = image.planes[0 ];
453+ int destIndex = 0 ;
454+ for (int row = 0 ; row < height; row++ ) {
455+ final int srcRowStart = row * yPlane.bytesPerRow;
456+ nv21.setRange (destIndex, destIndex + width, yPlane.bytes, srcRowStart);
457+ destIndex += width;
458+ }
459+
460+ // Interleave V and U planes into NV21 (VU order)
461+ final Plane uPlane = image.planes[1 ];
462+ final Plane vPlane = image.planes[2 ];
463+ final int uvPixelStride = uPlane.bytesPerPixel ?? 1 ;
464+ final int vPixelStride = vPlane.bytesPerPixel ?? 1 ;
465+
466+ int uvIndex = ySize;
467+ for (int row = 0 ; row < height ~ / 2 ; row++ ) {
468+ final int uRowStart = row * uPlane.bytesPerRow;
469+ final int vRowStart = row * vPlane.bytesPerRow;
470+
471+ for (int col = 0 ; col < width ~ / 2 ; col++ ) {
472+ final int uIndex = uRowStart + col * uvPixelStride;
473+ final int vIndex = vRowStart + col * vPixelStride;
474+
475+ nv21[uvIndex++ ] = vPlane.bytes[vIndex];
476+ nv21[uvIndex++ ] = uPlane.bytes[uIndex];
477+ }
478+ }
479+
480+ return nv21;
481+ }
385482}
0 commit comments