绘制从远程服务器下载的图像

时间:2018-11-08 08:28:41

标签: dart flutter

我需要执行以下操作:

  1. 从服务器下载PNG资源
  2. 根据状态在该图像上绘制几个矩形,并使用不同的颜色
  3. 在“可缩放图像视图”中显示该图像

我在使用Canvas的Android应用中有一个有效的代码,但是我不知道如何使用Flutter来实现。

以下是下载资源的代码:

string

下一步我该怎么做?我尝试使用PictureRecorder和Canvas,但找不到从画布上的文件绘制图像并将其转换为Image的方法,因为无法从文件中提取宽度和高度。

编辑: 下面是我想在Flutter中实现的等效Android代码。

static Future<File> getImageFromUrl(String url) async {
final directory = await getApplicationDocumentsDirectory();
final file = File("$directory/${_getSHA(url)}.png");

if (await file.exists()) {
  // Returns the cached file
} else {
  final response = await http.get(url);

  if (response.statusCode >= 200 && response.statusCode < 300) {
    await file.writeAsBytes(response.bodyBytes);
  } else {
    return null;
  }
}
return file;
}

2 个答案:

答案 0 :(得分:0)

通常,只显示互联网上的图像,就可以使用Image.network构造函数。如果要进一步自定义交互,例如基于加载状态显示矩形,则可以使用Image类,并将NetworkImage传递给其构造函数。 NetworkImage允许您侦听加载和错误事件。

要在图片上方绘制图片,我只是建议使用Stack小部件。

如果您想为图像添加缩放功能,则应考虑使用zoomable_imagephoto_view包来替换下面代码中的Image

此外,如果需要缓存,则可以使用cached_network_image软件包中的CachedNetworkImageProvider

下面的示例在加载图像上显示一个黄色矩形,在完全加载的图像上显示一个绿色矩形,如果加载崩溃,则显示一个红色矩形。这是一个完整的应用程序,您可以将其复制并粘贴到您的IDE中并进行尝试。

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;
    }
  }
}

答案 1 :(得分:0)

我终于设法解决了这个问题!

我创建了一个渲染器,该渲染器创建了一个合成图像(远程资源的背景,并在前景中添加了矩形)。

渲染器:

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();
  }
}

为了将图像提供给ZoomableImage,我创建了一个自定义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);
  }
}

如果有人知道将图像转换为编解码器甚至跳过此步骤的更好方法,请发表评论(MapRenderer.renderMap函数)。