Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add support for YUV_420_888 Image format. #732

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

panmari
Copy link
Contributor

@panmari panmari commented Jan 14, 2025

Casting the bytes to a type directly is not possible, thus allocating a new texture is necessary (and costly).

But on the bright side, we avoid the conversion inside the camera plugin [0].

[0] https://github.com/flutter/packages/blob/d1fd6232ec33cd5a25aa762e605c494afced812f/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/ImageStreamReaderUtils.java#L35

Casting the bytes to a type directly is not possible, thus allocating
a new texture is necessary (and costly).

But on the bright side, we avoid the conversion inside the camera
plugin [0].

[0] https://github.com/flutter/packages/blob/d1fd6232ec33cd5a25aa762e605c494afced812f/packages/camera/camera_android/android/src/main/java/io/flutter/plugins/camera/media/ImageStreamReaderUtils.java#L35
@panmari
Copy link
Contributor Author

panmari commented Jan 14, 2025

There's still the open issue of cleaning up the texture. I'd appreciate some feedback on how to change the API accordingly. I don't think there's a way around that.

@panmari panmari changed the title Add support for YUV_420_888 Image format. fix: Add support for YUV_420_888 Image format. Jan 14, 2025
@wantroba
Copy link

If you allow me, I will share with you the method I used to solve a similar problem. Maybe it can help:

import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:image/image.dart' as imglib;
import 'package:path_provider/path_provider.dart';
import 'package:flutter/material.dart';

class ImageUtils {
  static Future<imglib.Image?> convertToImage(CameraImage image) async {
    if (image.format.group == ImageFormatGroup.yuv420) {
      return convertYUV420toImageColor(image);
    } else {
      return imglib.Image.fromBytes(
        bytes: image.planes[0].bytes.buffer,
        width: image.width,
        height: image.height,
        order: imglib.ChannelOrder.bgra,
      );
    }
  }

  static Future<imglib.Image?> convertYUV420toImageColor(
      CameraImage image) async {
    try {
      final int width = image.width;
      final int height = image.height;
      final int uvRowStride = image.planes[1].bytesPerRow;
      final int uvPixelStride = image.planes[1].bytesPerPixel ?? 0;

      debugPrint("uvRowStride: $uvRowStride");
      debugPrint("uvPixelStride: $uvPixelStride");

      ReceivePort port = ReceivePort();
      final isolate = await Isolate.spawn<SendPort>((sendPort) {
        // imgLib -> Image package from https://pub.dartlang.org/packages/image
        // Create Image buffer
        // Fill image buffer with plane[0] from YUV420_888
        var img = imglib.Image(width: width, height: height);
        for (int x = 0; x < width; x++) {
          for (int y = 0; y < height; y++) {
            final int uvIndex =
                uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
            final int index = y * width + x;

            final yp = image.planes[0].bytes[index];
            final up = image.planes[1].bytes[uvIndex];
            final vp = image.planes[2].bytes[uvIndex];
            // Calculate pixel color
            int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255);
            int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91)
                .round()
                .clamp(0, 255);
            int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255);
            // color: 0x FF  FF  FF  FF
            //           A   B   G   R
            img.data?.setPixel(x, y, imglib.ColorRgb8(r, g, b));
          }
        }
        sendPort.send(img);
      }, port.sendPort);
      imglib.Image img = await port.first;
      isolate.kill(priority: Isolate.immediate);
      return img;
    } catch (e) {
      debugPrint(">>>>>>>>>>>> ERROR:$e");
    }
    return null;
  }

  static Future<File?> createFile(imglib.Image image) async {
    imglib.Command cmd = imglib.Command()
      ..image(image)
      ..encodePng();
    await cmd.executeThread();
    Uint8List? imageBytes = cmd.outputBytes;
    if (imageBytes != null) {
      cmd = imglib.Command()..decodePng(imageBytes);
      await cmd.executeThread();
      imglib.Image? originalImage = cmd.outputImage;
      int height = originalImage?.height ?? 0;
      int width = originalImage?.width ?? 0;
      var dir = await getApplicationDocumentsDirectory();
      File file = File(
          "${dir.path}/help_buy_app_${DateTime.now().millisecondsSinceEpoch}.png");
      // Let's check for the image size
      if (height >= width) {
        // I'm interested in portrait photos so
        // I'll just return here
        cmd = imglib.Command()
          ..decodePng(imageBytes)
          ..writeToFile(file.path);
        await cmd.executeThread();
        return file;
      }

      if (height < width && originalImage != null) {
        debugPrint('Rotating image necessary');
        cmd = imglib.Command()
          ..image(originalImage)
          ..copyRotate(angle: 90)
          ..writeToFile(file.path);
        await cmd.executeThread();
        return file;
      }
    }

    return null;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants