I am trying to get an local asset image loaded into an ui.Image object. But the ui.Image is an abstract class. I basically have this :
import 'dart:ui' as ui;
class MyImage implements ui.Image{
int height;
int width;
MyImage(String file){
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
String toString() {
// TODO: implement toString
return super.toString();
}
}
Using your code with the function definition Below. The error is at
' ui.Image image = await loadImage(img); ' await is underlined red and tool tip is 'Undefined name 'await' in function body not marked with async.'
class TrialApp extends StatefulWidget {
#override
_TrialAppState createState() => new _TrialAppState();
}
class _TrialAppState extends State<TrialApp> {
NodeWithSize rootNode;
#override
void initState() {
// TODO: implement initState
super.initState();
rootNode = new NodeWithSize(new Size(400.0, 400.0));
}
#override
Widget build(BuildContext context) {
// define a function that converts the I.Image object into ui.Image
//object
Future<ui.Image> loadImage(I.Image img) async {
final Completer<ui.Image> imageCompleter = new Completer();
ui.decodeImageFromList(img.getBytes(), (ui.Image img) {
imageCompleter.complete(img);
});
return imageCompleter.future;
}
// Obtain a `I.Image` object from the image file
I.Image img = I.decodeImage(new io.File('images/tile.png').readAsBytesSync());
// Obtain the `ui.Image` from the `I.Image` object
ui.Image image = await loadImage(img);
Sprite myButton = new Sprite.fromImage(image);
rootNode.addChild(myButton);
return new SpriteWidget(rootNode);
}
}
First obtain the image from the assetbundle using rootBundle. The convert the obtained ByteData to List<int>. Now you can obtain a ui.Image using the decodeImageFromList method.
Example:
// import statements
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:spritewidget/spritewidget.dart';
class TrialApp extends StatefulWidget {
#override
_TrialAppState createState() => new _TrialAppState();
}
class _TrialAppState extends State<TrialApp> {
NodeWithSize rootNode = new NodeWithSize(new Size(400.0, 400.0));
#override
void initState() {
super.initState();
init();
}
Future<Null> init() async {
rootNode = new NodeWithSize(new Size(400.0, 400.0));
// Read file from assetbundle
final ByteData data = await rootBundle.load('images/tile.png');
// Convert the obtained ByteData into ui.Image
final ui.Image image = await loadImage(new Uint8List.view(data.buffer)); // Uint8List converts the ByteData into List<int>
Sprite myButton = new Sprite.fromImage(image);
rootNode.addChild(myButton);
// notify to redraw with child
setState(() {
rootNode = rootNode;
});
}
// define a function that converts the List<int> into ui.Image object
Future<ui.Image> loadImage(List<int> img) async {
final Completer<ui.Image> imageCompleter = new Completer();
ui.decodeImageFromList(img, (ui.Image img) {
imageCompleter.complete(img);
});
return imageCompleter.future;
}
#override
Widget build(BuildContext context) {
return new Container(
color: Colors.white,
child: new SpriteWidget(rootNode),
);
}
}
Hope that helps!
If you are using SpriteWidget you can load images using the ImageMap class to save some code. From the SpriteWidget docuementation:
ImageMap images = new ImageMap(rootBundle);
// Load a single image
ui.Image image = await images.loadImage('assets/my_image.png');
// Load multiple images
await images.load(<String>[
'assets/image_0.png',
'assets/image_1.png',
'assets/image_2.png',
]);
// Access a loaded image from the ImageMap
image = images['assets/image_0.png'];
Related
I need to change color of the custompaint at certain intervals (like Timer.periodic) inside a camera app using foregroundpainter. How can I achieve it? I am able to print "timer" inside setstate() but color (r,g,b values) doesn't change.It appears that setstate() func gets processed (since I get "timer" printed) but custompaint doesn't change r,g,b values.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
List<CameraDescription> cameras;
Future<void> main() async {
cameras = await availableCameras();
runApp(CameraApp());
}
var temp;
class CameraApp extends StatefulWidget {
#override
_CameraAppState createState() => _CameraAppState();
}
var r = 255;
var g = 0;
var b = 0;
class _CameraAppState extends State<CameraApp> {
CameraController controller;
var crossPainter = CrossPainter(r, g, b);
#override
void initState() {
super.initState();
controller = CameraController(cameras[0],
ResolutionPreset.medium);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {
Timer(new Duration(seconds: 5), () {
r = 0;
g = 255;
b = 0;
print('timer');
crossPainter = CrossPainter(r, g, b);
});
});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
var t = Timer.periodic(new Duration(milliseconds: 500),
(Timer timer)=> CrossPainter(225,0,0));
#override
Widget build(BuildContext context) {
if (!controller.value.isInitialized) {
return Container();
}
return CustomPaint(
//foregroundPainter: CrossPainter(r, g, b),
foregroundPainter:crossPainter,
size:Size(100, 100),
willChange:true,
child: CameraPreview(controller));
}
}
class CrossPainter extends CustomPainter{
#override
Paint _paint;
CrossPainter(r,g,b){
_paint = Paint()
..color = Color.fromRGBO(r,g,b,1)
..strokeWidth = 2.0
..strokeCap = StrokeCap.round;
}
#override
void paint(Canvas canvas, Size size) {
var rect = Offset(100,0.0) & size;
canvas.drawRect(rect, _paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
In flutter, we want to update the display on a regular basis.
We can update the display when the value changes.
However, the set value does not change in this case.
Therefore, we do not know what to do as a trigger to update the display.
We changed from Stateless Widget to Stateful Widget. And we started the timer in initState() and canceled the timer in dispose(). We regularly call notifyListeners () on ScopedModel and are updating the drawing.
It works as expected. Unfortunately, it is not a beautiful way. It is hard to understand, it is annoying. Do not you know a better way? How should we do?
We will clarify our implementation example below. It is a minimal code.
xxxxx_widget.dart
import 'package:flutter/material.dart';
import 'package:pregnancy/scoped_model/xxxxx_model.dart';
import 'package:pregnancy/widgets/yyyyy_widget.dart';
import 'package:scoped_model/scoped_model.dart';
class XxxxxWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<XxxxxModel>(
model: XxxxxModel(),
child: YyyyyWidget(),
);
}
}
yyyyy_widget.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:pregnancy/scoped_model/xxxxx_model.dart';
import 'package:scoped_model/scoped_model.dart';
class YyyyyWidget extends StatefulWidget {
#override
YyyyyWidgetState createState() {
return new YyyyyWidgetState();
}
}
class YyyyyWidgetState extends State<YyyyyWidget> {
Timer _timer;
#override
void initState() {
_timer = Timer.periodic(
const Duration(milliseconds: 500),
(Timer t) {
XxxxxModel.of(context).notify();
},
);
super.initState();
}
#override
void dispose() {
_timer.cancel();
_timer = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<XxxxxModel>(
builder: (context, child, model) {
var diff = model.datetime.difference(DateTime.now());
var hours = diff.inHours.remainder(Duration.hoursPerDay);
var minutes = diff.inMinutes.remainder(Duration.minutesPerHour);
var seconds = diff.inSeconds.remainder(Duration.secondsPerMinute);
return Text('${hours} hours ${minutes} minutes ${seconds} seconds');
},
);
}
}
xxxxx_model.dart
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class XxxxxModel extends Model {
static XxxxxModel of(BuildContext context) =>
ScopedModel.of<XxxxxModel>(context);
DateTime _datetime = DateTime.now().add(Duration(days: 1));
get datetime => _datetime;
set datetime(DateTime value) {
_datetime = value;
notifyListeners();
}
void notify() {
notifyListeners();
}
}
You could use a StreamBuilder that listens to an interval stream. It automatically manages the subscription.
Your model can provide the stream as a property.
I am trying at add an image from a byte buffer, to a canvas element:
Uint8List image = bd.buffer.asUint8List(++offset, len);
await ui.decodeImageFromList(image, addIMage);
Where the callback function, addImage() is
void printImage(data){
print("Image stuff");
print(data);
canvasPainter.addItem(data);
}
Canvas painter is implements CustomPainter and is shown below:
class CanvasPainter implements CustomPainter{
var items = new List();
void addItems(var item){
items.add(item);}
#override
void paint(Canvas canvas, Size size){
for (var item in items){
canvas.drawImage(item,new Offset(50.0,50.0),new Paint();
}
...
}
class WriteScreen extends StatefulWidget {
ScreenState state;
#override
ScreenState createState() => state = new ScreenState();
}
class ScreenState extends State<WriteScreen> {
GestureDetector touch;
CustomPaint canvas;
CanvasPainter canvasPainter;
#override
void initState() {
super.initState();
canvasPainter = new CanvasPainter(const Color.fromRGBO(255, 255, 255, 1.0));
}
#override
Widget build(BuildContext context) {
touch = new GestureDetector(
onPanStart: panStart
);
final mediaQueryData = MediaQuery.of(context);
canvasPainter.size = mediaQueryData.size;
canvas = new CustomPaint( painter: canvasPainter);
}
}
I am receiving a compile time error in my CustomPainter class:
compiler message: lib/canvas_painter.dart:64:28: Error: A value of type '#lib1::Image' can't be assigned to a variable of type 'dart.ui::Image'.
compiler message: Try changing the type of the left hand side, or casting the right hand side to 'dart.ui::Image'.
compiler message: canvas.drawImage(item, new Offset(50.0, 50.0), strokePaint);
compiler message:
I am very confused by the error message, as I am importing Image from dart.ui so I am unsure as to why the compiler is throwing this exception.
Any help/suggestions would be much appreciated.
Add some types in CanvasPainter and update the question with any new error message.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
....
class CanvasPainter extends CustomPainter {
List<ui.Image> items = new List<ui.Image>();
void addItems(ui.Image item) {
items.add(item);
}
#override
void paint(Canvas canvas, Size size) {
for (ui.Image item in items) {
canvas.drawImage(item, new Offset(50.0, 50.0), new Paint());
}
}
}
How to load a huge bitmap in flutter? For example I have an image 3000x3000px that I need to display in Image widget with size 100x100px. In android SDK I can use BitmapOption.inSampleSize to load downscaled image. Is there have an analogue in Flutter SDK?
as an option you can use ResizeImage widget:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
ResizeImage img;
#override
void initState() {
super.initState();
img = ResizeImage(NetworkImage('https://picsum.photos/3000/3000'), width: 100, height: 100);
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Image(image: img)));
}
}
if you add this code to initState
img.resolve(ImageConfiguration()).addListener(ImageStreamListener((info, bool _)async {
var data = await info.image.toByteData();
print('==== ${img.width} ${img.height} ${data.buffer.asUint32List().length}');
}));
you will see in log something line this
==== 100 100 10000
I've successfully saved an image to my Firebase Storage reference. Now I need to download it. The examples I've seen are uploading and downloading in the same method, using the same StorageUploadTask with this line of code...
final Uri downloadUrl = (await uploadTask.future).downloadUrl;
My question is how can I get the downloadUrl from a separate method that doesn't require an uploadTask.future since I'm only uploading an image when a FirebaseUser updates their profile image?
StorageReference now has Future<dynamic> getDownloadURL() method. Retype result to String and use it with your NetworkImage widget:
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
class FirestoreImage extends StatefulWidget {
final StorageReference reference;
final Widget fallback;
final ImageProvider placeholder;
FirestoreImage(
{Key key,
#required this.reference,
#required this.fallback,
#required this.placeholder});
#override
FirestoreImageState createState() =>
FirestoreImageState(reference, fallback, placeholder);
}
class FirestoreImageState extends State<FirestoreImage> {
final Widget fallback;
final ImageProvider placeholder;
String _imageUrl;
bool _loaded = false;
_setImageData(dynamic url) {
setState(() {
_loaded = true;
_imageUrl = url;
});
}
_setError() {
setState(() {
_loaded = false;
});
}
FirestoreImageState(
StorageReference reference, this.fallback, this.placeholder) {
reference.getDownloadURL().then(_setImageData).catchError((err) {
_setError();
});
}
#override
Widget build(BuildContext context) => _loaded
? FadeInImage(
image: NetworkImage(_imageUrl),
placeholder: placeholder,
)
: fallback;
}
Old Answer:
I've just started developing in Flutter (Dart) so my answer will definitely not be perfect (maybe even bad) but here is how I did it:
import 'dart:typed_data';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
class FirestoreImage extends StatefulWidget {
final StorageReference _reference;
FirestoreImage(this._reference);
#override
FirestoreImageState createState() => FirestoreImageState(_reference);
}
class FirestoreImageState extends State<FirestoreImage> {
Uint8List _imageData;
_setImageData(Uint8List data) {
setState(() {
_imageData = data;
});
}
FirestoreImageState(StorageReference reference) {
reference
.getData(0x3FFFFFFF)
.then(_setImageData)
.catchError((err) {});
}
#override
Widget build(BuildContext context) =>
_imageData == null ? Container() : Image.memory(_imageData);
}
Now you can display FirestoreImage by calling new FirestoreImage(imageStorageReference). Maybe there is better way by extending Image
VizGhar provided a nice solution.
I've cleaned up the class, added some features and documentation.
It's available on this gist as well.
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
enum ImageDownloadState { Idle, GettingURL, Downloading, Done, Error }
class FirebaseStorageImage extends StatefulWidget {
/// The reference of the image that has to be loaded.
final StorageReference reference;
/// The widget that will be displayed when loading if no [placeholderImage] is set.
final Widget fallbackWidget;
/// The widget that will be displayed if an error occurs.
final Widget errorWidget;
/// The image that will be displayed when loading if no [fallbackWidget] is set.
final ImageProvider placeholderImage;
FirebaseStorageImage(
{Key key,
#required this.reference,
#required this.errorWidget,
this.fallbackWidget,
this.placeholderImage}) {
assert(
(this.fallbackWidget == null && this.placeholderImage != null) ||
(this.fallbackWidget != null && this.placeholderImage == null),
"Either [fallbackWidget] or [placeholderImage] must not be null.");
}
#override
_FirebaseStorageImageState createState() => _FirebaseStorageImageState(
reference, fallbackWidget, errorWidget, placeholderImage);
}
class _FirebaseStorageImageState extends State<FirebaseStorageImage>
with SingleTickerProviderStateMixin {
_FirebaseStorageImageState(StorageReference reference, this.fallbackWidget,
this.errorWidget, this.placeholderImage) {
var url = reference.getDownloadURL();
this._imageDownloadState = ImageDownloadState.GettingURL;
url.then(this._setImageData).catchError((err) {
this._setError();
});
}
/// The widget that will be displayed when loading if no [placeholderImage] is set.
final Widget fallbackWidget;
/// The widget that will be displayed if an error occurs.
final Widget errorWidget;
/// The image that will be displayed when loading if no [fallbackWidget] is set.
final ImageProvider placeholderImage;
/// The image that will be/has been downloaded from the [reference].
Image _networkImage;
/// The state of the [_networkImage].
ImageDownloadState _imageDownloadState = ImageDownloadState.Idle;
/// Sets the [_networkImage] to the image downloaded from [url].
void _setImageData(dynamic url) {
this._networkImage = Image.network(url);
this
._networkImage
.image
.resolve(ImageConfiguration())
.addListener((_, __) {
if (mounted)
setState(() => this._imageDownloadState = ImageDownloadState.Done);
});
if (this._imageDownloadState != ImageDownloadState.Done)
this._imageDownloadState = ImageDownloadState.Downloading;
}
/// Sets the [_imageDownloadState] to [ImageDownloadState.Error] and redraws the UI.
void _setError() {
if (mounted)
setState(() => this._imageDownloadState = ImageDownloadState.Error);
}
#override
Widget build(BuildContext context) {
switch (this._imageDownloadState) {
case ImageDownloadState.Idle:
case ImageDownloadState.GettingURL:
case ImageDownloadState.Downloading:
return Image(image: this.placeholderImage) ?? this.fallbackWidget;
case ImageDownloadState.Error:
return this.errorWidget;
case ImageDownloadState.Done:
return this._networkImage;
break;
default:
return this.errorWidget;
}
}
}
Not possible (yet). You need to store that uri yourself inside a database.
But you may and should use getData instead of using a download url within a firebase app.