How to avoid copying the bytes in Dart when deserializing an image from a Flatbuffer?

96 Views Asked by At

Summary:

When deserializing a simple flatbuffer containing an image (stored as [ubyte]), the generated Dart code returns a List<int> that is not a Uint8List. Therefore can't be cast to Uint8List, and when creating the Image, the bytes must be copied instead, using Uint8List.fromList().

While this does allow me to create the Image from a flatbuffer, the main reason I was using Flatbuffers in the first place was to avoid copying the bytes after loading the buffer. It means that it's better to avoid putting images in a flatbuffer, and instead to just load the image files individually, because that avoids the extra copy. I know, alternatively, that I can try Cap'n Proto or SBE, but I find it surprising that Flatbuffers has this issue, and suspect that perhaps I'm missing a key part.

Details:

For clarity, this example uses a minimal Flatbuffer schema. Eventually I want a downloadable resource bundle containing several other fields and many images, perhaps with nested structs or lists of images, but we'll start with this very simple schema containing just a string and one image:

fbuf_image.fbs:

table FbufImage {
  name: string;
  image1: [ubyte]; 
}
root_type FbufImage;

After running flatc --dart fbuf_image.fbs, the generated Dart code contains a class called FbufImage which looks like this:

The top part of fbuf_image_generated.dart:

class FbufImage {
  FbufImage._(this._bc, this._bcOffset);
  factory FbufImage(List<int> bytes) {
    final rootRef = fb.BufferContext.fromBytes(bytes);
    return reader.read(rootRef, 0);
  }

  static const fb.Reader<FbufImage> reader = _FbufImageReader();

  final fb.BufferContext _bc;
  final int _bcOffset;

  String? get name => const fb.StringReader().vTableGetNullable(_bc, _bcOffset, 4);
  List<int>? get image1 => const fb.Uint8ListReader().vTableGetNullable(_bc, _bcOffset, 6);

  @override
  String toString() {
    return 'FbufImage{name: ${name}, image1: ${image1}}';
  }
}
[...]

As you can see, the get image1 computed property getter returns a List<int>?.

This getter is used by the flutter app to get the bytes which are passed to the Image.memory() constructor. In the excerpt below, the flatbuffer is loaded from the assets rootBundle, then the image bytes are retrieved using buffer.image1:

In the Flutter app:

  final assetBytes = await rootBundle.load("fbuf/${name}.fbuf");
  final Uint8List buffer = assetBytes.buffer.asUint8List();
  if (buffer.length > 0) {
    final bundle = FbufImage(buffer);
    final name = bundle.name ?? "";
    if (bundle.image1 != null) {
      final bytes = bundle.image1 ?? [];
      if (bytes is Uint8List) {
        print("${name}.image1 is already a Uint8List, len=${bytes.length}");
        return Image.memory(bytes as Uint8List);
      }
      print("${name}.image1 is being copied, len=${bytes.length}");
      return Image.memory(Uint8List.fromList(bytes));
    }
  }

My app does create the Image but it ALWAYS prints bundle1.image1 is being copied, len=226933 IOW, it always does Uint8List.fromList(bytes) which copies the bytes.

If I rewrite the code to ignore the if (bytes is Uint8List) and try to coerce the cast (i.e. Image.memory(bytes as Uint8List)), then I get this runtime exception: Expected a value of type 'Uint8List', but got one of type '_FbUint8List'

Contrast this to the alternative of downloading individual image files which can be loaded and converted to Image objects without the copy:

  final Directory docDir = await getApplicationDocumentsDirectory();
  final File file = File('${docDir.path}/${relPath}/${fname}');
  final Uint8List buffer = await file.readAsBytes();
  if (buffer.length > 0) {
    return Image.memory(buffer);
  }

Q: So how do you avoid copying those image bytes using Flatbuffers?

Ultimately I'd like to have multiple images bundled in a Flatbuffer and be able to use Image.memory() create those images without copying.

Thanks!

[Edit: fixed a mistake in the Flutter deserialization code]

0

There are 0 best solutions below