[译]Flutter图片压缩插件flutter_image_compress

4,016 阅读4分钟

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」。

本文翻译自 flutter_image_compress | Flutter Package (pub.dev)


压缩图片的原生插件(Obj-C/Kotlin)

该库可以在 Android 和 iOS 上运行

为什么要使用 Dart 做这个?

Q:Dart 已经有了图片压缩库,为什么要做一个原生的? A:因为一些未知的原因,Dart 语言中的图片压缩效率不高,尤其是在发布版中。使用 isoalte 没有解决这个问题。

1.0.0

1.0.0 是空安全版本。

使用

dependencies:
  flutter_image_compress: ^1.0.0-nullsafety
import 'package:flutter_image_compress/flutter_image_compress.dart';

用法参考:完整示例

这里有一些使用该库 api 的方式:


  // 1. 压缩文件,返回Uint8List 。
  Future<Uint8List> testCompressFile(File file) async {
    var result = await FlutterImageCompress.compressWithFile(
      file.absolute.path,
      minWidth: 2300,
      minHeight: 1500,
      quality: 94,
      rotate: 90,
    );
    print(file.lengthSync());
    print(result.length);
    return result;
  }

  // 2. 压缩文件,返回文件 。
  Future<File> testCompressAndGetFile(File file, String targetPath) async {
    var result = await FlutterImageCompress.compressAndGetFile(
        file.absolute.path, targetPath,
        quality: 88,
        rotate: 180,
      );

    print(file.lengthSync());
    print(result.lengthSync());

    return result;
  }

  // 3. 压缩 asset,返回 Uint8List 。
  Future<Uint8List> testCompressAsset(String assetName) async {
    var list = await FlutterImageCompress.compressAssetImage(
      assetName,
      minHeight: 1920,
      minWidth: 1080,
      quality: 96,
      rotate: 180,
    );

    return list;
  }

  // 4. 压缩 Uint8List ,返回另外一个 Uint8List 。
  Future<Uint8List> testComporessList(Uint8List list) async {
    var result = await FlutterImageCompress.compressWithList(
      list,
      minHeight: 1920,
      minWidth: 1080,
      quality: 96,
      rotate: 135,
    );
    print(list.length);
    print(result.length);
    return result;
  }

关于共同参数

最小宽度和最小高度

minWidth 和 minHeight 用来约束图片的缩放。

例如:一个 4000 * 2000 的图片,minWidth 设为 1920, minHeight 设为 1080 ,计算会如下:

// 这里使用 dart 作为示例,真正的实现是 Kotlin 或 OC。
import 'dart:math' as math;

void main() {
  var scale = calcScale(
    srcWidth: 4000,
    srcHeight: 2000,
    minWidth: 1920,
    minHeight: 1080,
  );

  print("scale = $scale"); // 缩放比率 = 1.8518518518518519
  print("target width = ${4000 / scale}, height = ${2000 / scale}"); // 目标宽度 = 2160.0, height = 1080.0
}

double calcScale({
  double srcWidth,
  double srcHeight,
  double minWidth,
  double minHeight,
}) {
  var scaleW = srcWidth / minWidth;
  var scaleH = srcHeight / minHeight;

  var scale = math.max(1.0, math.min(scaleW, scaleH));

  return scale;
}

如果你的图片宽度小于最小宽度、高度小于最小高度,scale (缩放比)会是1,也就是大小不会变化。

rotate (旋转)

如果需要旋转图片,使用这个参数。

autoCorrectionAngle (自动矫正角度)

这个参数只在 0.5.0 版本后存在。

由于历史原因,这里可能会和 rotate 属性有冲突,需要自我修正。 将 rotate 改为 0 或将 autoCorrectionAngle 设为 false 。

quality (质量)

目标图片的质量。

如果 format 是 png,在 iOS 上会被忽略。

format

支持 jpeg 或 png ,默认是 jpeg 。

格式类标记为 enum CompressFormat

支持部分 Heif 和 webp 。

Webp

使用系统 api (速度很好)支持 Android。

尽管也支持 iOS,但是没有系统的实现,使用了第三方库,因为编码速度所以不建议使用。 将来,google 的 libwebp(c/c++) 可能会通过其它第三方库用来做编码工作,但是现在不能保证实现时间。

HEIF(Heic)

Heif for iOS

Only support iOS 11+.

Heif for Android

使用 HeifWriter 实现。

仅支持 API 28+

还可能需要硬件编码支持,不能保证所有 API28 以上的设备都可用。

inSampleSize

该参数仅支持 Anrdoid 。

关于该参数的描述,参考 Android 官方网站

keepExif

如果该参数为 true,EXIF 信息会保存在压缩后的文件中。

有以下几点需要注意:

  1. 默认值为 false 。
  2. 即使设为 true ,也不会包含方向属性。
  3. 仅支持 jpg 格式,不支持 PNG 格式。

结果

About List<int> and Uint8List 

运行时错误

例:

Future<Uint8List> compressAndTryCatch(String path) async {
    Uint8List result;
    try {
      result = await FlutterImageCompress.compressWithFile(path,
          format: CompressFormat.heic);
    } on UnsupportedError catch (e) {
      print(e);
      result = await FlutterImageCompress.compressWithFile(path,
          format: CompressFormat.jpeg);
    }
    return result;
  }

Android

可能需要升级 Kotlin 到 1.3.72 版本或更高。

iOS

暂时未发现问题。

Troubleshooting or common error

因为一些支持问题,所有的 API 需要兼容格式和系统兼容性,可能会抛出一个异常(UnsupportError)。所以如果 坚持使用 webp 和 heic 格式,请自己捕获异常,然后在不支持的设备上使用 jpeg 压缩。

Compressing returns null

有时,压缩处理会返回 null 。需要检查能否读写文件,还有目标文件的父文件夹必须存在。

例如:使用 path_provider 插件访问一些应用文件夹,还有权限插件在 Android/iOS 上请求访问 SD 卡。

Android build error 

Caused by: org.gradle.internal.event.ListenerNotificationException: Failed to notify project evaluation listener.
        at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:86)
        ...
Caused by: java.lang.AbstractMethodError
        at org.jetbrains.kotlin.gradle.plugin.KotlinPluginKt.resolveSubpluginArtifacts(KotlinPlugin.kt:776)
        ...

参考 flutter/flutter/issues#21473

需要升级 Kotlin 版本到 1.2.71+ (推荐1.3.72)。

如果将来 Flutter 支持更多的平台(Windows、 Mac、 Linux、等),而你还在使用该库,请提议 issue 或 PR!

About EXIF information

使用该库, EXIF 信息默认会被移除。 将 keepExif 设为 true 可保持 EXIF 信息,但是不会保留  direction 信息。

LICENSE

代码使用 MIT 风格的许可证

PNG/JPEG encoder

分别使用系统的 API 。

Webp encoder

在 iOS 上使用 SDWebImageWebPCoder 来编码 UIImage。 (MIT许可)

Android 代码使用 Android 的系统 api 。

HEIF encoder

在 iOS 上使用 iOS 系统的 api 。

在 AndroidP 或更高版本使用 HeifWriter(androidx component by Google) 编码。

About Exif handle code 

iOS 代码从 dvkch/SYPictureMetadata 复制,许可证

Android 代码从 flutter/plugin/image_picker 复制,做了一些改动。(BSD 3 风格许可证)