Convert base64 string to Image class and back

Issue

I have a simple table, which I would like to create and maintain via MOOR. Below is the table definition:

class Plants extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get name => text()();
  TextColumn get image => text().map(const ImageConverter()).nullable()();
}

As you can see, I have an image field, and I would like to use Image to base64 conversion and back. Please, note I refer to Flutter Image class, not the widget.
I already found the topic, where conversion between Image widget and base64 took place, but it does not seem to me right from an architectural point of view to use widget on the data layer.
This is what I have done so far:

import 'package:image/image.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:moor/moor.dart';

class ImageConverter extends TypeConverter<Image, String> {
  Image _imageFromBase64String(String base64String) {
    return null; // <--- missing implementation
  }

  Uint8List _dataFromBase64String(String base64String) {
    return base64Decode(base64String);
  }

  String _base64String(Uint8List data) {
    return base64Encode(data);
  }

  const ImageConverter();
  @override
  Image mapToDart(String fromDb) {
    if (fromDb == null) {
      return null;
    }
    return _imageFromBase64String(fromDb);
  }

  @override
  String mapToSql(Image value) {
    if (value == null) {
      return null;
    }

    return _base64String(value.getBytes());
  }
}

As you can see, I have no problem (I guess) to implement Image to base64 conversion, I struggle however to implement base64 to Image.

Solution

Note that the link in the question is incorrect. The question refers to the Image class from the image package – see https://pub.dev/documentation/image/latest/image/Image-class.html

You need a pair of functions like this:

Image b64ToImage(int width, int height, String b64) {
  return Image.fromBytes(width, height, base64.decode(b64));
}

String imageToB64(Image image) => base64.encode(image.getBytes());

getBytes returns you an array of NxM 32 bit pixels (by default in RGBA order) but gives you no idea of the geometry of the image. A 100×200 image will return an 80,000 byte array – but so will a 200×100 image! When you re-construct the image you need to tell it the original width and height.

This is why file formats like BMP exist. They prefix the pixel array with a header that contain information like width, height, pixel depth, pixel order, etc. The package has a BMP decoder, but no encoder. You could implement a simple BMP encoder if you wanted to encode the width and height into the byte array and thus into the base64 string. (See this partial answer.)

Answered By – Richard Heap

Answer Checked By – Dawn Plyler (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.