在Flutter中,如何确定图片的宽度和高度?

63
假设我已经在我的pubspec.yaml中声明了图片,像这样:

Assume I have declared my image in my pubspec.yaml like this:

  assets:
    - assets/kitten.jpg

我的Flutter代码如下:

void main() {
  runApp(
    new Center(
      child: new Image.asset('assets/kitten.jpg'),
    ),
  );
}

现在我有一个 new Image.asset(),我该如何确定该图像的宽度和高度?例如,我只想打印出图像的宽度和高度。

(看起来 dart:ui 的 Image 类 有宽度和高度,但不确定如何从小部件的 Image 转到 dart:ui 的 Image。)

谢谢!


你的解决方案完美地运行了 :) 谢谢 - Besart D. Laci
13个回答

116
其他答案似乎过于复杂,如果你只想在异步函数中获取图像的宽度和高度。你可以直接使用Flutter库来获取图像的分辨率,像这样:
import 'dart:io';
import 'dart:ui';

File image = new File('image.png'); // Or any other way to get a File instance.
var decodedImage = await decodeImageFromList(image.readAsBytesSync());
print(decodedImage.width);
print(decodedImage.height);

3
这会返回比图片实际高度更多的高度。当我使用这个值来设置高度时,顶部和底部会出现很多额外的空白。 - sakina
2
截至2020年7月26日,最简单的解决方案仍然有效。 - DaReal
5
我不理解这部分内容。我在assets文件夹中有一张图片,但它说无法读取该图片。 File image = new File("lib/pages/assets/images/road_map.png"); - Michael Tolsma
1
你好!如果图像在资产中,我该如何提供文件路径?@qwertzguy - nrion
4
@nrion 我认为你可以使用 rootBundle.load('assets/image.png') 并将其直接传递给 decodeImageFromList。 - qwertzguy
显示剩余8条评论

59

更新的解决方案:

随着Flutter的新版本发布,旧的解决方案已经过时。现在addListener需要一个ImageStreamListener

import 'dart:ui' as ui;

Widget build(BuildContext context) {
    Image image = Image.network('https://istack.dev59.com/lkd0a.webp');
    Completer<ui.Image> completer = Completer<ui.Image>();
    image.image
      .resolve(ImageConfiguration())
      .addListener(
          ImageStreamListener(
              (ImageInfo info, bool _) => completer.complete(info.image)));
    ...
    ...

如果您已经拥有一个Image小部件,您可以通过调用其ImageProvider上的resolve方法来读取其中的ImageStream

screenshot

import 'dart:ui' as ui;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(new MaterialApp(
    home: new MyHomePage(),
  ));
}

class MyHomePage extends StatelessWidget {

  Widget build(BuildContext context) {
    Image image = new Image.network('https://istack.dev59.com/lkd0a.webp');
    Completer<ui.Image> completer = new Completer<ui.Image>();
    image.image
      .resolve(new ImageConfiguration())
      .addListener((ImageInfo info, bool _) => completer.complete(info.image));
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Image Dimensions Example"),
      ),
      body: new ListView(
        children: [
          new FutureBuilder<ui.Image>(
            future: completer.future,
            builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
              if (snapshot.hasData) {
                return new Text(
                  '${snapshot.data.width}x${snapshot.data.height}',
                  style: Theme.of(context).textTheme.display3,
                );
              } else {
                return new Text('Loading...');
              }
            },
          ),
          image,
        ],
      ),
    );
  }
}

13
我们需要更新这个答案。addListener 需要一个 ImageStreamListener。即使加上它,我在这一行 completer.complete(info.image) 会出现一个错误,指出 The argument type 'Image' can't be assigned to the parameter type 'FutureOr<Image> - Ionel Lupu
如果离线,如何计算大小? - John Joe
2
这会导致图片被加载两次,从而浪费大量的网络和CPU吗? - ch271828n
4
需要的代码是:image.image.resolve(ImageConfiguration()).addListener( ImageStreamListener((ImageInfo info, bool synchronousCall) { completer.complete(info.image); })); - Gregory Ray
@lonel Lupu 导入ui库,并将ui.Image用作泛型类型的Completer。 - blob

30
创建一个方法,例如:
Future<Size> _calculateImageDimension() {
  Completer<Size> completer = Completer();
  Image image = Image.network("https://istack.dev59.com/lkd0a.webp");
  image.image.resolve(ImageConfiguration()).addListener(
    ImageStreamListener(
      (ImageInfo image, bool synchronousCall) {
        var myImage = image.image;
        Size size = Size(myImage.width.toDouble(), myImage.height.toDouble());
        completer.complete(size);
      },
    ),
  );
  return completer.future;
}

然后像这样使用:

_calculateImageDimension().then((size) => print("size = ${size}")); // 487.0,696.0

注意:当您使用此代码时,无法保存图像 -- image.writeAsBytes(result) 会静默失败。 - Elia Weiss
如果离线,如何计算? - John Joe
1
如果您有一个文件,请使用Image.file(your_file) - CopsOnRoad

22
你可以使用resolve方法来获取一个ImageProvider,然后使用addListener方法监听图像何时准备好。你也可以查看ImageStream以了解更多信息。

screenshot

import 'dart:ui' as ui;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(new MaterialApp(
    home: new MyHomePage(),
  ));
}

class MyHomePage extends StatelessWidget {

  Future<ui.Image> _getImage() {
    Completer<ui.Image> completer = new Completer<ui.Image>();
    new NetworkImage('https://istack.dev59.com/lkd0a.webp')
      .resolve(new ImageConfiguration())
      .addListener((ImageInfo info, bool _) => completer.complete(info.image));
    return completer.future;
  }

  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Image Dimensions Example"),
      ),
      body: new Center(
        child: new FutureBuilder<ui.Image>(
          future: _getImage(),
          builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
            if (snapshot.hasData) {
              ui.Image image = snapshot.data;
              return new Text(
                '${image.width}x${image.height}',
                style: Theme.of(context).textTheme.display4);
            } else {
              return new Text('Loading...');
            }
          },
        ),
      ),
    );
  }
}

谢谢!所以基本上如果我想知道宽度和高度,我不能使用图像小部件? - Seth Ladd
我没有说那个!我会添加另一个答案来展示另一种方法。 - Collin Jackson
这是否完全解压图像,还是只从流中读取图像元数据? - Luke Hutchison

10

使用Flutter的新版本,旧解决方案无法工作,例如:

  image.image
  .resolve(new ImageConfiguration())
  .addListener((ImageInfo info, bool _) => completer.complete(info.image));

以下是可工作版本:

_image.image
    .resolve(new ImageConfiguration())
    .addListener(new ImageStreamListener((ImageInfo image, bool _) {
  completer.complete(image.image);
}));

2
我确认这是添加监听器的新方式,但我在 completer.complete(image.image); 上遇到了一个错误:The argument of type Image can't be assigned to the type parameter FutureOr<Image>。这个问题该如何解决? - Ionel Lupu

9

一个只想解码图像边界的方法:

import 'dart:ui' as ui;

final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
final descriptor = await ui.ImageDescriptor.encoded(buffer);

final imageWidth = descriptor.width;
final imageHeight = descriptor.height;
print("imageWidth: $imageWidth, imageHeight: $imageHeight");

descriptor.dispose();
buffer.dispose();

1
你从哪里得到“字节”?如果您直接写下这些内容,它真的会帮助很多。猜测需要花费很多时间。 - Karolina Hagegård
@KarolinaHagegård bytes 是图像的字节数据。例如,您可以通过 File('your_image_path').readAsBytesSync() 从图像文件中获取字节。 - Summerly
谢谢,但请编辑您的答案以包含完整的代码集! - Karolina Hagegård
@Summerly 谢谢,这个方法比 decodeImageFromList 快得多。我已经测试了这个方法和 decodeImageFromList,这个方法快了5倍。 - Mohsen Haydari
截至2023年,decodeImageFromList()解决方案现在返回void并接受回调,其中该解决方案将更直观地返回具有宽度和高度的描述符。 - Matthew Rideout
这正是我在寻找的,我不想在了解边界之前解码图像。这样我就可以读取图像的宽高比和尺寸,然后解码一个缩小版本的图像,最大限度地使用cacheWidthcacheHeight来节省内存。谢谢! - Luke Hutchison

8

如果您不想使用FutureBuilder或then,则也可以像这样使用await:

别忘了导入Image。但由于有两个Image类,请像这样导入并使用ui.Image一起使用。

 import 'dart:ui' as ui

接下来,您可以按以下方式获取图像的尺寸。

 final Image image = Image(image: AssetImage('assets/images/someimage.png'));
 Completer<ui.Image> completer = new Completer<ui.Image>();
    image.image
        .resolve(new ImageConfiguration())
        .addListener(new ImageStreamListener((ImageInfo image, bool _) {
      completer.complete(image.image);
    }));
 ui.Image info = await completer.future;
 int width = info.width;
 int height = info.height;

2
绝对比被接受的答案更好。 - Paolo
如果你从一个Image开始,为什么不直接调用Image.widthImage.height呢? - Luke Hutchison

5

这是一个方便的帮助函数,基于其他解决方案实现

帮助函数

Future<ImageInfo> getImageInfo(Image img) async {
  final c = new Completer<ImageInfo>();
  img.image
    .resolve(new ImageConfiguration())        
    .addListener(new ImageStreamListener((ImageInfo i, bool _) {
      c.complete(i);
    }));
  return c.future;    
}

用法

Image image = Image.network("https://example.com/pic.png");
ImageInfo info = await getImageInfo(image);

3

检查从资源加载的图像尺寸的简单方法。

var img = await rootBundle.load("Your image path");
var decodedImage = await decodeImageFromList(img.buffer.asUint8List());
int imgWidth = decodedImage.width;
int imgHeight = decodedImage.height;

1
await 可以在 async 函数中使用,您能描述一下这段代码如何工作吗?或者提供一个使用它的示例代码。 - Mamrezo

0

这些解决方案是否适用于Flutter Web?我找不到任何解决方案,如果我尝试使用image_size_getter包,它会抛出“错误:不支持操作:_Namespace”。


不要回答其他问题,最好是针对你的问题提出新的问题,而不会得到任何反馈。 - pmatatias
如果您有新的问题,请通过单击提问按钮来提出。如果它有助于提供上下文,请包含此问题的链接。- 来自审核 - Fastnlight

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接