flutter_cached_network_image
flutter_cached_network_image copied to clipboard
How do I get the image bytes from the imageBuilder's imageProvider
🚀 Feature Requests
Contextualize the feature
How do I get the image bytes from the imageBuilder's imageProvider
Describe the feature
How do I get the image bytes from the imageBuilder's imageProvider
Platforms affected (mark all that apply)
- [x ] :iphone: iOS
- [x ] :robot: Android
HI
Here's how I figured to retrieve bytes from imageBuilder's imageProvider:
First, I created an extension on the ImageProvider type which define the method to extract bytes:
extension ImageTool on ImageProvider {
Future<Uint8List?> getBytes(BuildContext context, {ImageByteFormat format = ImageByteFormat.rawRgba}) async {
final imageStream = resolve(createLocalImageConfiguration(context));
final Completer<Uint8List?> completer = Completer<Uint8List?>();
final ImageStreamListener listener = ImageStreamListener(
(imageInfo, synchronousCall) async {
final bytes = await imageInfo.image.toByteData(format: format);
if (!completer.isCompleted) {
completer.complete(bytes?.buffer.asUint8List());
}
},
);
imageStream.addListener(listener);
final imageBytes = await completer.future;
imageStream.removeListener(listener);
return imageBytes;
}
}
Then I called it inside my widget in order to store image bytes to use them later:
class MyCircleAvatar extends StatelessWidget {
const MyCircleAvatar({Key? key, required this.imageUrl, this.diameter = 40}) : super(key: key);
final String imageUrl;
final double diameter;
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: imageUrl,
imageBuilder: (context, imageProvider) {
final format = imageUrl.toLowerCase().contains('png') ? ImageByteFormat.png : ImageByteFormat.rawRgba;
imageProvider.getBytes(context, format: format).then((imageBytes) {
if (imageBytes != null && imageBytes.isNotEmpty) {
// DO WHAT YOU WANT WITH YOUR BYTES
print(imageBytes.length);
}
});
return CircleAvatar(
backgroundImage: imageProvider,
radius: diameter / 2,
);
},
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, dynamic error) => const Icon(Icons.error_outline),
maxWidthDiskCache: (diameter * MediaQuery.of(context).devicePixelRatio).toInt(),
);
},
);
}
}
Thank you for your help ,I was originally a gray graph, I want to get the bytes of the image and return the image with the color by converting it
@MrDiezDOTcom This is my conversion code. I want to return the new image to imageBuilder, but I can't seem to do that
imageBuilder: (context, imageProvider) {
Img.Image? image = Img.decodePng(bytes);
int? width = image?.width;
int? height = image?.height;
// 新建一张四通道的图,图数据是三通道图的数据,这样的话此图保存就是四通道了
Img.Image newImg = Img.Image.fromBytes(width!, height!, image!.data);
// 对符合条件的像素进行修改完成渲染
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
// pixel是Uint32,以#AABBGGRR形式
int pixel = newImg.getPixel(w, h);
// 白色(255, 255, 255, 255) 变成透明(255, 255, 255, 0) (RGBA)
if (pixel == Img.Color.fromRgba(255, 199, 255, 255)) {
newImg.setPixelRgba(w, h, 255, 255, 255, 0);
}
// 白色边界线(252,252,252,255) 变成浅色一点
else if (pixel == Img.Color.fromRgba(252, 196, 252, 255)) {
newImg.setPixelRgba(w, h, 150, 150, 150, 0);
}
// 黑色(0, 0, 0, 255)变成草的颜色(16, 188, 147, 255) (RGBA)
else if (pixel == Img.Color.fromRgba(0, 200, 0, 255)) {
newImg.setPixelRgba(w, h, 16, 188, 147, 255);
}
// 切割过的颜色(100, 100, 100, 255) 变成(84, 74, 96, 255) (RGBA)
else if (pixel == Img.Color.fromRgba(100, 44, 100, 255)) {
newImg.setPixelRgba(w, h, 84, 74, 96, 255);
}
}
}
return CircleAvatar(
backgroundImage: Uint8List.fromList(Img.encodePng(newImg)),
radius: diameter / 2,
);
},
imageBuilder should return a Widget
Here the widget is CircleAvatar which take an ImageProvider at his property backgroundImage, but it seems you give it an Uint8List instead.
Try this:
...
return CircleAvatar(
backgroundImage: MemoryImage(Uint8List.fromList(Img.encodePng(newImg))),
radius: diameter / 2,
);
MemoryImage inherit from ImageProvider
@MrDiezDOTcom This is all the code, I tried to return newImg to imageBuilder, but because it is asynchronous, it does not work, look forward to your help
CachedNetworkImage(
imageUrl: '${_mapInfo.imgUrl}',
imageBuilder: (context, imageProvider) {
// final format =
// _mapInfo.imgUrl.toLowerCase().contains('png') ? ImageByteFormat.png : ImageByteFormat.rawRgba;
var byts;
imageProvider.getBytes(context, format: ImageByteFormat.png).then((imageBytes) {
if (imageBytes != null && imageBytes.isNotEmpty) {
// DO WHAT YOU WANT WITH YOUR BYTES
byts = imageBytes;
Img.Image? image = Img.decodePng(imageBytes);
int? width = image?.width;
int? height = image?.height;
Img.Image newImg = Img.Image.fromBytes(width!, height!, image!.data);
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int pixel = newImg.getPixel(w, h);
if (pixel == Img.Color.fromRgba(255, 199, 255, 255)) {
newImg.setPixelRgba(w, h, 255, 255, 255, 0);
} else if (pixel == Img.Color.fromRgba(252, 196, 252, 255)) {
newImg.setPixelRgba(w, h, 150, 150, 150, 0);
} else if (pixel == Img.Color.fromRgba(0, 200, 0, 255)) {
newImg.setPixelRgba(w, h, 16, 188, 147, 255);
} else if (pixel == Img.Color.fromRgba(100, 44, 100, 255)) {
newImg.setPixelRgba(w, h, 84, 74, 96, 255);
}
}
}
byts = Uint8List.fromList(Img.encodePng(newImg));
return Uint8List.fromList(Img.encodePng(newImg));
}
});
return Image.memory(byts);
},
)
@MrDiezDOTcom The new image cannot be returned as a component to CachedNetworkImage for presentation
You can use a StatefulWidget to set asynchronous bytes to a variable. Then display these bytes when they're available otherwise display a loader.
Here is a complete working example:
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img_lib;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
Text('PNG image:'),
MyCachedNetworkImage(
imageUrl:
'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/640px-PNG_transparency_demonstration_1.png',
),
],
),
Column(
children: [
Text('JPG image:'),
MyCachedNetworkImage(
imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/3/3f/JPEG_example_flower.jpg',
),
],
),
],
),
),
),
);
}
}
extension ImageTool on ImageProvider {
Future<Uint8List?> getBytes(BuildContext context, {ImageByteFormat format = ImageByteFormat.rawRgba}) async {
final imageStream = resolve(createLocalImageConfiguration(context));
final Completer<Uint8List?> completer = Completer<Uint8List?>();
final ImageStreamListener listener = ImageStreamListener(
(imageInfo, synchronousCall) async {
final bytes = await imageInfo.image.toByteData(format: format);
if (!completer.isCompleted) {
completer.complete(bytes?.buffer.asUint8List());
}
},
);
imageStream.addListener(listener);
final imageBytes = await completer.future;
imageStream.removeListener(listener);
return imageBytes;
}
}
class MyCachedNetworkImage extends StatefulWidget {
const MyCachedNetworkImage({Key? key, required this.imageUrl}) : super(key: key);
final String imageUrl;
@override
State<MyCachedNetworkImage> createState() => _MyCachedNetworkImageState();
}
class _MyCachedNetworkImageState extends State<MyCachedNetworkImage> {
Uint8List? _imageBytes;
bool get _isImageBytes => _imageBytes != null && _imageBytes!.isNotEmpty;
String? _error;
bool get _isError => _error != null && _error!.isNotEmpty;
Widget _getLoaderWidget() => const CircularProgressIndicator();
Widget _getErrorWidget([String? error]) {
final text = _error ?? error;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline),
if (text != null && text.isNotEmpty) Text(text),
],
);
}
img_lib.Image _decodeImageBytes(Uint8List originalImageBytes) {
late final img_lib.Image? image;
final decoder = img_lib.findDecoderForData(originalImageBytes);
if (decoder == null) throw Exception('Image not supported');
image = decoder.decodeImage(originalImageBytes);
if (image == null) throw Exception('Unable to decode image');
return image;
}
Uint8List _encodeImage(img_lib.Image newImage) {
return Uint8List.fromList(img_lib.encodePng(newImage));
}
img_lib.Image _transformImage(img_lib.Image originalImage) {
int width = originalImage.width;
int height = originalImage.height;
img_lib.Image newImage = img_lib.Image.fromBytes(width, height, originalImage.data);
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int pixel = newImage.getPixel(w, h);
if (pixel == img_lib.Color.fromRgba(255, 199, 255, 255)) {
newImage.setPixelRgba(w, h, 255, 255, 255, 0);
} else if (pixel == img_lib.Color.fromRgba(252, 196, 252, 255)) {
newImage.setPixelRgba(w, h, 150, 150, 150, 0);
} else if (pixel == img_lib.Color.fromRgba(0, 200, 0, 255)) {
newImage.setPixelRgba(w, h, 16, 188, 147, 255);
} else if (pixel == img_lib.Color.fromRgba(100, 44, 100, 255)) {
newImage.setPixelRgba(w, h, 84, 74, 96, 255);
}
}
}
return newImage;
}
void _handleImageBytes(Uint8List? originalImageBytes) {
try {
if (originalImageBytes != null && originalImageBytes.isNotEmpty) {
setState(() {
_imageBytes = _encodeImage(_transformImage(_decodeImageBytes(originalImageBytes)));
});
} else {
throw Exception('Empty bytes list provided');
}
} catch (e) {
setState(() {
_error = e.toString();
});
}
}
@override
Widget build(BuildContext context) {
return _isError
? _getErrorWidget(_error)
: _isImageBytes
? Image.memory(_imageBytes!)
: CachedNetworkImage(
imageUrl: widget.imageUrl,
imageBuilder: (context, imageProvider) {
imageProvider.getBytes(context, format: ImageByteFormat.png).then(_handleImageBytes);
return _getLoaderWidget();
},
placeholder: (context, url) => _getLoaderWidget(),
errorWidget: (context, url, dynamic error) => _getErrorWidget(error.toString()),
);
}
}
@MrDiezDOTcom Thank you for your help