Drawing over an image downloaded from remote server
up vote
2
down vote
favorite
I need to do the following:
- Download a PNG resource from a server
- Draw several rectangles over that image with different color depending on state
- Display that image in Zoomable Image View
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;
}
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.
EDIT:
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);
dart flutter
add a comment |
up vote
2
down vote
favorite
I need to do the following:
- Download a PNG resource from a server
- Draw several rectangles over that image with different color depending on state
- Display that image in Zoomable Image View
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;
}
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.
EDIT:
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);
dart flutter
use Image.network() constructor
– pskink
Nov 8 at 8:31
I saw that constructor, but I need to cache the file and also draw over it so I need to get access to the canvas.
– RMK
Nov 8 at 9:30
so useCachedNetworkImageProvider
from here and call itsresolve
method
– pskink
Nov 8 at 9:37
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I need to do the following:
- Download a PNG resource from a server
- Draw several rectangles over that image with different color depending on state
- Display that image in Zoomable Image View
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;
}
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.
EDIT:
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);
dart flutter
I need to do the following:
- Download a PNG resource from a server
- Draw several rectangles over that image with different color depending on state
- Display that image in Zoomable Image View
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;
}
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.
EDIT:
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);
dart flutter
dart flutter
edited Nov 8 at 12:57
asked Nov 8 at 8:28
RMK
642510
642510
use Image.network() constructor
– pskink
Nov 8 at 8:31
I saw that constructor, but I need to cache the file and also draw over it so I need to get access to the canvas.
– RMK
Nov 8 at 9:30
so useCachedNetworkImageProvider
from here and call itsresolve
method
– pskink
Nov 8 at 9:37
add a comment |
use Image.network() constructor
– pskink
Nov 8 at 8:31
I saw that constructor, but I need to cache the file and also draw over it so I need to get access to the canvas.
– RMK
Nov 8 at 9:30
so useCachedNetworkImageProvider
from here and call itsresolve
method
– pskink
Nov 8 at 9:37
use Image.network() constructor
– pskink
Nov 8 at 8:31
use Image.network() constructor
– pskink
Nov 8 at 8:31
I saw that constructor, but I need to cache the file and also draw over it so I need to get access to the canvas.
– RMK
Nov 8 at 9:30
I saw that constructor, but I need to cache the file and also draw over it so I need to get access to the canvas.
– RMK
Nov 8 at 9:30
so use
CachedNetworkImageProvider
from here and call its resolve
method– pskink
Nov 8 at 9:37
so use
CachedNetworkImageProvider
from here and call its resolve
method– pskink
Nov 8 at 9:37
add a comment |
2 Answers
2
active
oldest
votes
up vote
0
down vote
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;
}
}
}
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
TheStack
renders the rectangles over theImage
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?
– Niklas
Nov 8 at 12:58
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
You can surround the rectangle with anIgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.
– Niklas
Nov 8 at 13:15
add a comment |
up vote
0
down vote
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).
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
0
down vote
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;
}
}
}
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
TheStack
renders the rectangles over theImage
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?
– Niklas
Nov 8 at 12:58
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
You can surround the rectangle with anIgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.
– Niklas
Nov 8 at 13:15
add a comment |
up vote
0
down vote
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;
}
}
}
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
TheStack
renders the rectangles over theImage
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?
– Niklas
Nov 8 at 12:58
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
You can surround the rectangle with anIgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.
– Niklas
Nov 8 at 13:15
add a comment |
up vote
0
down vote
up vote
0
down vote
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;
}
}
}
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;
}
}
}
answered Nov 8 at 9:44
Niklas
1607
1607
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
TheStack
renders the rectangles over theImage
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?
– Niklas
Nov 8 at 12:58
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
You can surround the rectangle with anIgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.
– Niklas
Nov 8 at 13:15
add a comment |
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
TheStack
renders the rectangles over theImage
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?
– Niklas
Nov 8 at 12:58
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
You can surround the rectangle with anIgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.
– Niklas
Nov 8 at 13:15
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
Thank you for reply, It will be useful to know that. What I need is to draw over the Image that is already loaded. So let's say I have a PNG image of a square and depending on data from REST API I need to draw a rectangle over it with different color.
– RMK
Nov 8 at 12:51
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
I edited my question and attached Android code that I want to port to Flutter
– RMK
Nov 8 at 12:57
The
Stack
renders the rectangles over the Image
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?– Niklas
Nov 8 at 12:58
The
Stack
renders the rectangles over the Image
it should be what you wanted in first place? It justs depends on the loading state and not on the state of the REST API. Adjusting the rectangle sizes etc. should'nt be the problem?– Niklas
Nov 8 at 12:58
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
Oh, now I see what you mean. But how the zoomable image view will behave in case of stack? How to make the rectangles stay above the correct position of an image?
– RMK
Nov 8 at 13:12
You can surround the rectangle with an
IgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.– Niklas
Nov 8 at 13:15
You can surround the rectangle with an
IgnorePointer
Widget, which will let the hits pass through. Scaling both, the rectangle and the image, should not be a big problem.– Niklas
Nov 8 at 13:15
add a comment |
up vote
0
down vote
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).
add a comment |
up vote
0
down vote
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).
add a comment |
up vote
0
down vote
up vote
0
down vote
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).
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).
answered Nov 8 at 22:24
RMK
642510
642510
add a comment |
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53203904%2fdrawing-over-an-image-downloaded-from-remote-server%23new-answer', 'question_page');
}
);
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
use Image.network() constructor
– pskink
Nov 8 at 8:31
I saw that constructor, but I need to cache the file and also draw over it so I need to get access to the canvas.
– RMK
Nov 8 at 9:30
so use
CachedNetworkImageProvider
from here and call itsresolve
method– pskink
Nov 8 at 9:37