绘制从远程服务器下载的图像
我需要执行以下操作:
- 从服务器下载PNG资源
- 根据状态在该图像上绘制几个矩形,用不同的颜色
- 在可缩放图像视图中显示该图像
我在使用Canvas的Android应用中有一个有效的代码,但我不知道如何使用Flutter做到这一点。
I have a working code in an Android app using Canvas, but I cannot figure out how to do that with Flutter.
以下是下载资源的代码:
Here is the code that downloads the resource:
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;
}
下一步我该怎么做?我尝试使用PictureRecorder和Canvas,但找不到从画布上的文件绘制图像并将其转换为Image的方法,因为无法从文件中提取宽度和高度。
What should I do next? I tried using PictureRecorder and Canvas, but I cannot find a way to draw image from the file on those canvas and then convert it to Image because I cannot extract width and height from the file.
编辑:
下面是我要在Flutter中实现的等效Android代码。
Below is the Android code equivalent that I would like to implement in Flutter.
// Here we have a bitmap from a file
Bitmap mapBitmap = getBitmap();
Canvas mapCanvas = new Canvas(mapBitmap);
mapDrawable.setBounds(0, 0, mapCanvas.getWidth(), mapCanvas.getHeight());
mapDrawable.draw(mapCanvas);
canvasWidth = mapCanvas.getWidth();
canvasHeight = mapCanvas.getHeight();
Paint paint = new Paint();
for (java.util.Map.Entry<String, MapObject> entry : this.mapObjects.entrySet()) {
MapObject mapObject = entry.getValue();
paint.setColor(getContext().getResources().getColor(mapObject.getBackgroundColor()));
paint.setAlpha(100);
mapCanvas.drawRect((int) (mapObject.getPosX() * scaleX),
(int) (mapObject.getPosY() * scaleY),
(int) ((mapObject.getPosX() + mapObject.getWidth()) * scaleX),
(int) ((mapObject.getPosY() + mapObject.getHeight()) * scaleY),
paint);
}
photoView.setImageBitmap(mapBitmap);
我终于设法解决了这个问题!
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).
渲染器:
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:
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);
}
}
如果有人知道将图像转换为更好的方法编解码器,或者甚至跳过此步骤,请发表评论(MapRenderer.renderMap函数)。
If anybody knows a better way to convert an Image to Codec or to even skip this step, please comment (MapRenderer.renderMap function).