How to use an ui.Image as an ImageProvider data source

Yarm picture Yarm · Nov 26, 2019 · Viewed 8.3k times · Source

I can set a standard flutter image from memory or from a file, but not an ImageProvider as required by the PhotoView framework.

The PhotoView framework accepts an AssetImage as a type of provider but not any of the other ImageProvider types (File and Memory).

What might be a workaround? I'd think the framework would support image sources other than from a project's assets

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photo_view/photo_view.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';

import 'dart:ui' as ui;
import 'dart:typed_data';
import 'dart:async';
import 'dart:io';


class TestImageDraw extends StatefulWidget {
  TestImageDraw({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _TestImageDrawState createState() => _TestImageDrawState();
}

class _TestImageDrawState extends State<TestImageDraw> {
  ImageProvider _imageProvider;

  @override
  void initState() {
    super.initState();

    _imageProvider = NetworkImage(
        "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png");
  }

  _generateImage() {
    GenImage.generateImage().then((generatedImage) async {
      ByteData image =
          await generatedImage.toByteData(format: ui.ImageByteFormat.png);

      // String base64 = base64Encode(image.buffer.asInt64List());
      // print(base64);
      // Uint8List bytes = base64Decode(base64);
      // _imageProvider = MemoryImage(bytes);

      _imageProvider = MemoryImage(image.buffer.asUint8List());

      setState(() {});
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: Container(
          child: Column(children: <Widget>[
        //Image will display
        Image(image: _imageProvider),
        Container(
            child: _imageProvider != null
                ? PhotoView(imageProvider: _imageProvider)
                : Container()),
      ])),
      floatingActionButton: new FloatingActionButton(
        onPressed: _generateImage,
        tooltip: 'Generate',
        child: new Icon(Icons.add),
      ),
    );
  }
}

class GenImage {
  static Future<ui.Image> generateImage() async {
    ui.PictureRecorder recorder = new ui.PictureRecorder();
    Canvas c = new Canvas(recorder);
    var rect = new Rect.fromLTWH(0.0, 0.0, 200.0, 200.0);
    c.clipRect(rect);

    final paint = new Paint();
    paint.strokeWidth = 1;
    paint.color = const Color(0xFF0000FF);
    paint.style = PaintingStyle.stroke;

    final offset = new Offset(100.0, 100.0);
    c.drawCircle(offset, 50.0, paint);
    var picture = recorder.endRecording();
    final image = await picture.toImage(500, 500);
    return image;
  }
}

Answer

Renan C. Araujo picture Renan C. Araujo · Nov 27, 2019

PhotoView author here, the widget works fine with all ImageProvider (Memory and File extend ImageProvider).

The problem is how you are creating the provider. image.buffer.asUint8List() prints a headless map of bits, making it impossible to display. The head of a bitmap contains information such size of each pixel (in bits) and the size of the image (in pixels). I've actually created a whole package around that.

With bitmap package, you can retrieve a headful file from an ui.Image instance:

First, create a bitmap instance:

ByteData bytedata = await image.toByteData();
Bitmap bitmap = Bitmap.fromHeadless(imageWidth, imageHeight, bytedata.buffer.asUint8List());

Then, recover the final map of bits:

Uint8List headedIntList = bitmap.buildHeaded();

Now you can pass it into PhotoView:

PhotoView(imageProvider: headedIntList)