ImageShader image size is too large - flutter

I'm currently trying to change the shape of an image using ImageShader.
I am getting the image with _loadUiImage() below.
import 'dart:ui' as ui;
Future<ui.Image> _loadUiImage(String url) async {
final response = await http.get(Uri.parse(url));
final completer = Completer<ui.Image>();
ui.decodeImageFromList(response.bodyBytes, completer.complete);
return completer.future;
}
It then displays the acquired image in its own form.
FutureBuilder(
future: loadUiImage("https://img.freepik.com/free-photo/peak-bamboo-lijiang-rural-mist_1417-410.jpg"),
builder: ((context, snapshot) => ShaderMask(
blendMode: BlendMode.srcATop,
shaderCallback: (bounds) =>
ImageShader(snapshot.data!, TileMode.clamp, TileMode.clamp, Matrix4.identity().storage),
child: _hoge, // some widget
));
However, the size of the image is too large compared to the size of the filter, like below.
I want the image to be smaller like the image below instead of like the one above.

As #pskink commented, The problem was Matrix4.identity().
I replaced it to Matrix4.diagonal3Values(x, y, z) then this was solved.
Thank you pskink!

Related

Flutter Network Image fails with "invalid image data" on some images

I am scraping some images from any kind of websites. And I need to calculate their Size. For that I found that function which is working for most of the cases:
Future<Size> calculateImageDimension(String imageUrl) {
Completer<Size> completer = Completer();
late Image image;
image = Image.network(
imageUrl,
);
image.image.resolve(const ImageConfiguration()).addListener(
ImageStreamListener(
(ImageInfo image, bool synchronousCall) {
var myImage = image.image;
Size size = Size(myImage.width.toDouble(), myImage.height.toDouble());
if (!completer.isCompleted) {
completer.complete(size);
}
},
),
);
return completer.future;
}
Problem:
The function above fails with
Exception: Invalid image data
when calling it with this imageUrl:
https://static.zara.net/photos///contents/cm/media-transformations/joinlife-ctx/joinlife-large.svg?ts=1611919362013
What's the issue here? Also I couldn't catch the Exeption...
I found this related question. But its not helping me either.
I found this issue on Git and it works for my quite well. It is rather a workaround than a solution:
Future<bool> hasValidImageData(String url,BuildContext context) async {
bool hasNoError=true;
var output=Completer<bool>();
precacheImage(
NetworkImage(url),
context,
onError: (e,stackTrace)=>hasNoError=false,
).then((_)=>output.complete(hasNoError));
return output.future;
}
it makes use of the onError that precacheImage come with. A downside is that it needs the BuildContext but like I said, its working for me and the only workaround I found.

How to load image from assets folder inside a pdf in Flutter web?

We want to show image on a pdf from assets folder in Flutter web application:
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:flutter/material.dart';
.............
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
height: 400,
width: 900,
child: PdfPreview(
build: (format) => _generatePdf(format, "SOME TITLE"),
)),
),
]));
}
Future<Uint8List> _generatePdf(PdfPageFormat format) async {
final pdf = pw.Document();
pdf.addPage(
pw.Page(
pageFormat: format,
build: (context) {
return pw.Image(AssetImage('assets/imglogo.png')); //This line gives the error
}));
return pdf.save();
}
This code gives error:
The argument type 'AssetImage' can't be assigned to the parameter type 'ImageProvider'
The documentation addresses only two cases To load an image from a file:(dart.io is not supported on the web), and To load an image from the network using the printing package:, which is not the case, so we tried the solutions provided here: 1,2, but each one gives a different exception.
Is there another approach to achieve this?
You can convert your ByteData directly to Uint8List as shown in the example code below. This can then be passed to the MemoryImage constructor:
Future<void> addPage(pw.Document pdf, String filename) async {
final imageByteData = await rootBundle.load('assets/$filename');
// Convert ByteData to Uint8List
final imageUint8List = imageByteData.buffer
.asUint8List(imageByteData.offsetInBytes, imageByteData.lengthInBytes);
final image = pw.MemoryImage(imageUint8List);
pdf.addPage(
pw.Page(
build: (pw.Context context) {
return pw.Center(
child: pw.Image(image),
); // Center
},
),
);
}
To achieve this you can get the asset image as a file, and then use this file in the PDF. If I take your code, we can add a function to get a File representation of your asset image :
Future<File> getImageFileFromAssets(String path) async {
final byteData = await rootBundle.load('assets/$path');
final file = File('${(await getTemporaryDirectory()).path}/$path');
await file.writeAsBytes(byteData.buffer
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes));
return file;
}
To make it works, you need to add path_provider to your dependencies.
Then you can use this function in your pdf generation function :
final pdf = pw.Document();
final file = await getImageFileFromAssets(yourpath);
final image = pw.MemoryImage(
file.readAsBytesSync(),
);
pdf.addPage(pw.Page(
pageFormat: format,
build: (context) {
return pw.Image(image);
}));
return pdf.save();

Flutter ShaderMask using an ImageShader

I'm trying to get a ShaderMask that uses an Image as a Shader. I'm reading the image from memory, so my image is a File. How do I create an ImageShader using an image from memory?
File imageFile;
Image image = Image.file(imageFile)
ShaderMask(
shaderCallback: (bounds) {
Float64List matrix4 = new Matrix4.identity().storage; // <--- DO I NEED THIS OR THE BOUNDS?
return ImageShader(image, TileMode.mirror, TileMode.mirror, matrix4);
},
child: child
)
There's an error with ImageShader as image is the wrong type (I need ui.Image, which I don't understand how to create).
How do I create the ImageShader from a File image?
PS: is matrix4 correct or should I use the bounds somehow?
The important thing to know is that ImageShader uses an Image from the dart:ui package and not an instance of the Image widget. There is no direct operation or constructor available to create an instance of ui.Image from a network location, a file or asset, so you need some code to get it done.
The best generic solution i came up with after looking up many resources and digging into the code how the Image widget is loading raw images, is to use an ImageProvider as source. The abstract ImageProvider has implementations like NetworkImage, FileImage, ExactAssetImage and MemoryImage to load your image from any resource you want.
First you get an ImageStream using the ImageProvider.resolve method. The resolve method takes an argument of type ImageConfiguration, that should be filled with as many information as you have available at the code location. You can use the global createLocalImageConfiguration function in most cases, but be aware that this will not work when you create the shader in the initState method of a StatefulWidget.
On the resolved ImageStream you can attach an ImageStreamListener, that takes an ImageListener callback as first parameter. Once the image is loaded, the callback will be called with an ImageInfo, which provides the requested image on the image property.
You can construct the ImageShader with both tile modes as TileMode.clamp and a simple identity matrix, which you can either create by hand or take that one offered by the Matrix4 class. If you need the image shader to be smaller than the size of the provided image, you can wrap your provider in an ResizeProvider and specify the desired width and height.
Below my implementation of an ImageMask widget as a reference, which can be used to mask widgets of any kind.
class ImageMask extends StatefulWidget {
final ImageProvider image;
final double width;
final double height;
final Widget child;
const ImageMask({#required this.image, this.width, this.height, #required this.child});
#override
_ImageMaskState createState() => _ImageMaskState();
}
class _ImageMaskState extends State<ImageMask> {
Future<Shader> _shader;
#override
void initState() {
super.initState();
_shader = _loadShader(context);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _shader,
builder: (_, AsyncSnapshot<Shader> snapshot) {
return snapshot.connectionState != ConnectionState.done || snapshot.hasError
? SizedBox(width: widget.width, height: widget.height)
: ShaderMask(
blendMode: BlendMode.dstATop,
shaderCallback: (bounds) => snapshot.data,
child: widget.child,
);
},
);
}
Future<Shader> _loadShader(BuildContext context) async {
final completer = Completer<ImageInfo>();
// use the ResizeImage provider to resolve the image in the required size
ResizeImage(widget.image, width: widget.width.toInt(), height: widget.height.toInt())
.resolve(ImageConfiguration(size: Size(widget.width, widget.height)))
.addListener(ImageStreamListener((info, _) => completer.complete(info)));
final info = await completer.future;
return ImageShader(
info.image,
TileMode.clamp,
TileMode.clamp,
Float64List.fromList(Matrix4.identity().storage),
);
}
}

Caching Image on flutter web, is it required?

Due to CachedNetworkImage not working on flutter web, upon porting, I have tried to use this, but my question is do we really need this? Or we just use Image. Network and the browser and service worker will handle the cache part (which is then set by server's response header through, for example, cache-control= "max-age=43200, public"
This is used on the food delivery project I am working on, https://www.santaiyamcha.com
Below are the classes I use to replace CachedNetworkImage which doesn't seem to work well.
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:http_extensions_cache/http_extensions_cache.dart';
import 'package:http_extensions/http_extensions.dart';
/// Builds a widget when the connectionState is none and waiting
typedef LoadingBuilder = Widget Function(BuildContext context);
/// Builds a if some error occurs
typedef ErrorBuilder = Widget Function(BuildContext context, Object error);
class MeetNetworkImage extends StatelessWidget {
/// Image url that you want to show in your app.
final String imageUrl;
/// When image data loading from the [imageUrl],
/// you can build specific widgets with [loadingBuilder]
final LoadingBuilder loadingBuilder;
/// When some error occurs,
/// you can build specific error widget with [errorBuilder]
final ErrorBuilder errorBuilder;
final double scale;
final double width;
final double height;
final Color color;
final FilterQuality filterQuality;
final BlendMode colorBlendMode;
final BoxFit fit;
final AlignmentGeometry alignment;
final ImageRepeat repeat;
final Rect centerSlice;
final bool matchTextDirection;
/// Whether to continue showing the old image (true), or briefly show nothing
/// (false), when the image provider changes.
final bool gaplessPlayback;
final String semanticLabel;
final bool excludeFromSemantics;
MeetNetworkImage({
#required this.imageUrl,
this.loadingBuilder = null,
this.errorBuilder = null,
this.scale = 1.0,
this.height,
this.width,
this.color = const Color(0xFDFFFF),
this.fit = BoxFit.fill,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.semanticLabel,
this.centerSlice,
this.colorBlendMode,
this.excludeFromSemantics = false,
this.filterQuality = FilterQuality.low,
this.matchTextDirection = false,
this.gaplessPlayback = false,
}) : assert(imageUrl != null),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null);
Future<Response> getUrlResponse() {
/*
//The caching part I tried, does not seems working
final client = ExtendedClient(
inner: Client(),
extensions: [
CacheExtension(
//logger: Logger("Cache"),
defaultOptions: CacheOptions(
expiry: const Duration(hours: 168),
// The duration after the cached result of the request will be expired.
//forceUpdate: false, // Forces to request a new value, even if an valid cache is available
//forceCache: false, // Forces to return the cached value if available (even if expired).
//ignoreCache: true, //Indicates whether the request should bypass all caching logic
//returnCacheOnError: true, //If [true], on error, if a value is available in the store if is returned as a successful response (even if expired).
keyBuilder: (request) => "${request.method}_${imageUrl.toString()}",
// Builds the unqie key used for indexing a request in cache.
store: MemoryCacheStore(),
// The store used for caching data.
shouldBeSaved: (response) =>
response.statusCode >= 200 && response.statusCode < 300,
),
)
],
);
return client.get(imageUrl);
*/
return get(imageUrl);
}
Widget getLoadingWidget(BuildContext context) {
if (loadingBuilder != null) {
return loadingBuilder(context);
} else
return Container(
height: height, width: width,
child: Center(
child: CircularProgressIndicator()
)
/*Image.asset(
'assets/img/loading4.gif',
height: height,
width: width,
fit: BoxFit.contain,
),*/
);
}
Widget getErrorWidget(BuildContext context, String error) {
if (errorBuilder != null) {
return errorBuilder(context, error);
} else
return Center(child: Icon(Icons.error));
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getUrlResponse(),
builder: (BuildContext context, AsyncSnapshot<Response> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return getLoadingWidget(context);
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.hasError)
return getErrorWidget(context, snapshot.error);
if (!snapshot.hasData)
return getErrorWidget(context, snapshot.error);
//return getLoadingWidget(context);
return Image.memory(
snapshot.data.bodyBytes,
scale: scale,
height: height,
width: width,
color: color,
fit: fit,
alignment: alignment,
repeat: repeat,
centerSlice: centerSlice,
colorBlendMode: colorBlendMode,
excludeFromSemantics: excludeFromSemantics,
filterQuality: filterQuality,
gaplessPlayback: gaplessPlayback,
matchTextDirection: matchTextDirection,
semanticLabel: semanticLabel,
);
}
return Container();
},
);
}
}
What do you suggest?
I am using FadeInImage.memoryNetwork. It is working fine. The browser handles the cache part.
No. This is an issue with Google Chrome which prefers "cache-control" over E-Tags or Last Modified Headers. In my case I am using Firefox which caches on the basis of E-Tags. So here is the thing.
If you are using setState((){}) or the flutter engine calls the setState due to some reason, the images are rebuilt and if the images are not cached, it is again fetched. To prevent it, use the header cache-control: max-age=<any value greater than 0> and it will work fine in Chrome.
Or just build the app using web renderer canvaskit - flutter build web --web-renderer canvaskit.
I have come to this conclusion based on my experience and I could not find it anywhere, Hope it helps :)
Try to update your flutter version if you are using old one, and use from image providers interface.
And the browser will take care of the rest
Example:
Create a container add a decoration in the decoration add an image decoration
Then add from memory or network image.

Loading a list of images in one-by-one sequence

After getting a list of image urls from firebase using StreamBuilder
i used a ListView.builder with a TransitionToImage from this package:
https://pub.dartlang.org/packages/flutter_advanced_networkimage
and made a list of images.
here is my Streambuilder / builder code :
builder: (_, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) return const Text('loading...');
var doc = snapshot.data;
List<String> pages = List.from(doc['pages']);
return ListView.builder(
itemCount: pages.length,
itemBuilder: (context, index) {
return TransitionToImage(
AdvancedNetworkImage("a url", timeoutDuration: Duration(minutes: 1)),
placeholder: CircularProgressIndicator(),
duration: Duration(milliseconds: 300),
);
});
},
the ListView.builder loads all the images in random order at once, which doesn't look that natural for me or the users.
is there a way to make ListView.builder load a image, after the image in the previous index has been loaded?
I would use precacheImage to load the images in pages one-by-one and when arrived replace the URL in pages so that the real image is shown instead of a placeholder.
for(final page in pages) {
await precacheImage(page.downloadUrl, context);
page.displayUrl = page.downloadUrl;
setState(() => pages = pages.toList();
}