Drawing over an image downloaded from remote server Drawing over an image downloaded from remote server dart dart

Drawing over an image downloaded from remote server


I finally managed to solve the issue!

I created a renderer that creates a composite image (background from the remote resource and adds rectangles in the foreground).

The renderer:

class MapRenderer {  ui.Image _mapBackgroundImage;  Future<ui.Codec> renderMap(String url, List<Sensor> sensors) async {    await _loadMapBackground(url);    var renderedMapImage = await _updateSensors(sensors);    var byteD = await renderedMapImage.toByteData(        format: ui.ImageByteFormat.png);    return ui.instantiateImageCodec(Uint8List.view(byteD.buffer));  }  Future<ui.Image> _updateSensors(List<Sensor> sensors) async {    ui.PictureRecorder recorder = ui.PictureRecorder();    Canvas c = Canvas(recorder);    var paint = ui.Paint();    c.drawImage(_mapBackgroundImage, ui.Offset(0.0, 0.0), paint);    for (Sensor s in sensors) {      paint.color = (s.availability ? CustomColors.npSensorFree : CustomColors          .npSensorOccupied);      c.drawRect(        ui.Rect.fromPoints(ui.Offset(s.posX, s.posY),            ui.Offset(s.posX + s.width, s.posY + s.height)),        paint,      );    }    return recorder        .endRecording()        .toImage(_mapBackgroundImage.width, _mapBackgroundImage.height);  }  Future<void> _loadMapBackground(String url) async {    var imageBytes = await _getLocalCopyOrLoadFromUrl(url);    if (imageBytes != null) {      _mapBackgroundImage = await _getImageFromBytes(imageBytes);    } else {      return null;    }  }  Future<ui.Image> _getImageFromBytes(Uint8List bytes) async {    var imageCodec = await ui.instantiateImageCodec(bytes);    var frame = await imageCodec.getNextFrame();    return frame.image;  }  Future<Uint8List> _getLocalCopyOrLoadFromUrl(String url) async {    final directory = await getApplicationDocumentsDirectory();    final file = File("${directory.path}/${_getSHA(url)}.png");    if (await file.exists()) {      return await file.readAsBytes();    } else {      Uint8List resourceBytes = await _loadFromUrl(url);      if (resourceBytes != null) {        await file.writeAsBytes(resourceBytes);        return resourceBytes;      } else {        return null;      }    }  }  Future<Uint8List> _loadFromUrl(String url) async {    final response = await http.get(url);    if (response.statusCode >= 200 && response.statusCode < 300) {      return response.bodyBytes;    } else {      return null;    }  }  String _getSHA(String sth) {    var bytes = utf8.encode(sth);    var digest = sha1.convert(bytes);    return digest.toString();  }  void dispose() {    _mapBackgroundImage.dispose();  }}

And to supply the image to the ZoomableImage I created a custom ImageProvider:

class MapImageProvider extends ImageProvider<MapImageProvider> {  final String url;  final List<Sensor> sensors;  final MapRenderer mapRenderer = MapRenderer();  MapImageProvider(this.url, this.sensors);  @override  ImageStreamCompleter load(MapImageProvider key) {       return MultiFrameImageStreamCompleter(        codec: _loadAsync(key),        scale: 1.0,        informationCollector: (StringBuffer information) {          information.writeln('Image provider: $this');          information.write('Image key: $key');        });  }  Future<ui.Codec> _loadAsync(MapImageProvider key) async {    assert(key == this);    return await mapRenderer.renderMap(url, sensors);  }  @override  bool operator ==(Object other) =>      identical(this, other) ||      other is MapImageProvider &&          runtimeType == other.runtimeType &&          url == other.url;  @override  int get hashCode => url.hashCode;  @override  String toString() => '$runtimeType("$url")';    @override  Future<MapImageProvider> obtainKey(ImageConfiguration configuration) {    return SynchronousFuture<MapImageProvider>(this);  }}

If anybody knows a better way to convert an Image to Codec or to even skip this step, please comment (MapRenderer.renderMap function).


In general to simply display an image from the internet you can use the Image.network constructor. If you want to further customize the interaction, for example showing rectangles based on its loading state, you can use the Image class and pass a NetworkImage to its constructor. The NetworkImage allows you to listen to loading and error events.

To draw above the Image I would simply suggest using the Stack Widget.

If you wanna add zooming functionality to the image, you should consider using the zoomable_image or photo_view package to replace the Image in the code below.

Also, if caching is necessary you can use the CachedNetworkImageProvider from the cached_network_image package.

The example below shows a yellow rectangle on a loading image, a green rectangle on a fully loaded image and a red rectangle if the loading crashed. It is a full application, you can copy & paste it in your IDE and try it out.

import 'package:flutter/material.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Network Image Download',      theme: ThemeData(),      home: MainPage(),    );  }}class MainPage extends StatefulWidget {  @override  State<StatefulWidget> createState() => MainPageState();}class MainPageState extends State<MainPage> {  ImageProvider provider;  bool loaded;  bool error;  @override  void initState() {    super.initState();    loaded = false;    error = false;    provider = NetworkImage('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png');    provider.resolve(ImageConfiguration()).addListener((_, __) {      setState(() {        loaded = true;      });    }, onError: (_, __) {      setState(() {        error = true;      });    });  }  @override  Widget build(BuildContext context) {    return Scaffold(      body: Center(        child: Stack(          alignment: Alignment.center,          children: <Widget>[            Image(image: provider),            Container(              width: 75.0,              height: 75.0,              color: colorByState(),            )          ],        ),      ),    );  }  Color colorByState() {    if (error) {      return Colors.red;    } else if (loaded) {      return Colors.green;    } else {      return Colors.yellow;    }  }}