Search code examples
flutterdartflutter-web

Flutter Web: Convert ui.Image to ImageProvider


I'm using CustomPainter to draw something to a canvas.

Later I want to display that drawing in an Image widget (which takes an image parameter of type ImageProvider<Object>).

After some research I have found out a way to get there in basically two steps:

  1. create a ui.Image object from the canvas drawing
  2. convert that ui.Image to an ImageProvider that can be passed to an Image widget
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:bitmap/bitmap.dart';

class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    //drawing some stuff here
    //canvas.drawRect(...);
  }
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;

  Future<ui.Image> getImage() {
    final ui.PictureRecorder recorder = ui.PictureRecorder();
    try {
      //500 x 500 pixels
      paint(Canvas(recorder), const Size(500, 500));
    } catch (e) {
      debugPrint(e.toString());
    }

    final ui.Picture picture = recorder.endRecording();
    final img = picture.toImage(500, 500);
    return img;
  }

  static Future<ImageProvider> getProviderFromImage(ui.Image image) async {
    final ByteData? bytedata = await image.toByteData();
    if (bytedata == null) {
      return Future.error("some error msg");
    }

    final Bitmap bitmap = Bitmap.fromHeadless(
        image.width, image.height, bytedata.buffer.asUint8List());
    final Uint8List headedIntList = bitmap.buildHeaded();
    return MemoryImage(headedIntList);
  }
} 

The problem here is that this won't work for Flutter Web because the bitmap package does not have web support.

I already tried converting the ByteData to Uint8List without the bitmap package:

  static Future<ImageProvider> getProviderFromImage(ui.Image image) async {
    final ByteData? bytedata = await image.toByteData();
    if (bytedata == null) {
      return Future.error("some error msg");
    }
 
    final Uint8List headedIntList =
        Uint8List.view(bytedata.buffer);  
    return MemoryImage(headedIntList);
  }

But the resulting ImageProvider won't be accepted by the Image widget:

════════ Exception caught by image resource service ════════════════════════════ The following ImageCodecException was thrown resolving an image codec: Failed to decode image data. Image source: encoded image bytes


Solution

  • My approach actually works if the format parameter of toByteData() is passed:

      static Future<ImageProvider> getProviderFromImage(ui.Image image) async {
        final ByteData? bytedata = await image.toByteData(format: ui.ImageByteFormat.png);
        if (bytedata == null) {
          return Future.error("some error msg");
        }
     
        final Uint8List headedIntList =
            Uint8List.view(bytedata.buffer);  
        return MemoryImage(headedIntList);
      }