Luban 2 Flutter:一行代码在 Flutter 开发中实现图片压缩功能

17 阅读4分钟

Luban 2 Flutter —— 高效简洁的 Flutter 图片压缩插件,像素级还原微信朋友圈压缩策略。

📑 目录

📖 项目描述

开源地址:Gitee | Github

目前做 App 开发总绕不开图片这个元素。但是随着手机拍照分辨率的提升,图片的压缩成为一个很重要的问题。单纯对图片进行裁切,压缩已经有很多文章介绍。但是裁切成多少,压缩成多少却很难控制好,裁切过头图片太小,质量压缩过头则显示效果太差。

于是自然想到 App 巨头"微信"会是怎么处理,Luban(鲁班)就是通过在微信朋友圈发送近100张不同分辨率图片,对比原图与微信压缩后的图片逆向推算出来的压缩算法。

因为是逆向推算,效果还没法跟微信一模一样,但是已经很接近微信朋友圈压缩后的效果,具体看以下对比!

本库是 LubanFlutter 版本,使用 TurboJPEG 进行高性能图片压缩,提供简洁易用的 API 和接近微信朋友圈的压缩效果。

📊 效果与对比

图片类型原图(分辨率, 大小)Luban(分辨率, 大小)Wechat(分辨率, 大小)
标准拍照3024×4032, 5.10MB1440×1920, 305KB1440×1920, 303KB
高清大图4000×6000, 12.10MB1440×2160, 318KB1440×2160, 305KB
2K 截图1440×3200, 2.10MB1440×3200, 148KB1440×3200, 256KB
超长记录1242×22080, 6.10MB758×13490, 290KB744×13129, 256KB
全景横图12000×5000, 8.10MB1440×600, 126KB1440×600, 123KB
设计原稿6000×6000, 6.90MB1440×1440, 263KB1440×1440, 279KB

🔬 核心算法特性

本库采用自适应统一图像压缩算法 (Adaptive Unified Image Compression),通过原图的分辨率特征,动态应用差异化策略,实现画质与体积的最优平衡。

智能分辨率决策

  • 高清基准 (1440p):默认以 1440px 作为短边基准,确保在现代 2K/4K 屏幕上的视觉清晰度
  • 全景墙策略:自动识别超大全景图(长边 >10800px),锁定长边为 1440px,保留完整视野
  • 超大像素陷阱:对超过 4096万像素的超高像素图自动执行 1/4 降采样处理
  • 长图内存保护:针对超长截图建立 10.24MP 像素上限,通过等比缩放防止 OOM

自适应比特率控制

  • 极小图 (<0.5MP):几乎不进行有损压缩,防止压缩伪影
  • 高频信息图 (0.5-1MP):提高编码质量,补偿分辨率损失
  • 标准图片 (1-3MP):应用平衡系数,对标主流社交软件体验
  • 超大图/长图 (>3MP):应用高压缩率,显著减少体积

健壮性保障

  • 膨胀回退:压缩后体积大于原图时,自动透传原图,确保绝不"负优化"
  • 输入防御:妥善处理极端分辨率输入(0、负数、1px 等),防止崩溃

📦 安装

pubspec.yaml 文件中添加依赖:

dependencies:
  luban: ^2.0.1

然后运行:

flutter pub get

💻 使用

压缩单张图片

使用 File 对象

import 'dart:io';
import 'package:luban/luban.dart';

Future<void> compressImage() async {
  final file = File('/path/to/image.jpg');
  final result = await Luban.compress(file);
  
  if (result.isSuccess) {
    final compressionResult = result.value;
    print('压缩完成');
    print('原图大小: ${compressionResult.originalSizeKb} KB');
    print('压缩后大小: ${compressionResult.compressedSizeKb} KB');
    print('压缩率: ${(compressionResult.compressionRatio * 100).toStringAsFixed(1)}%');
    print('输出文件: ${compressionResult.file.path}');
  } else {
    print('压缩失败: ${result.error}');
  }
}

使用字符串路径

import 'package:luban/luban.dart';

Future<void> compressImage() async {
  final result = await Luban.compressPath('/path/to/image.jpg');
  
  result.fold(
    (error) => print('压缩失败: $error'),
    (compressionResult) {
      print('压缩完成,大小: ${compressionResult.compressedSizeKb} KB');
      print('输出文件: ${compressionResult.file.path}');
    },
  );
}

指定输出文件

import 'dart:io';
import 'package:luban/luban.dart';

Future<void> compressImage() async {
  final inputFile = File('/path/to/image.jpg');
  final outputFile = File('/path/to/output/compressed.jpg');
  
  final result = await Luban.compressToFile(inputFile, outputFile);
  
  if (result.isSuccess) {
    final compressionResult = result.value;
    print('压缩完成,文件已保存到: ${compressionResult.file.path}');
  }
}

指定输出目录

import 'dart:io';
import 'package:luban/luban.dart';

Future<void> compressImage() async {
  final inputFile = File('/path/to/image.jpg');
  final outputDir = Directory('/path/to/output');
  
  final result = await Luban.compress(inputFile, outputDir: outputDir);
  
  if (result.isSuccess) {
    final compressionResult = result.value;
    print('压缩完成,文件已保存到: ${compressionResult.file.path}');
  }
}

批量压缩图片

批量压缩返回 Result<BatchCompressionResult>,需要先检查成功或失败状态,然后访问 BatchCompressionResult 获取所有图片的压缩结果。

使用文件列表

import 'dart:io';
import 'package:luban/luban.dart';

Future<void> compressBatchImages() async {
  final files = [
    File('/path/to/image1.jpg'),
    File('/path/to/image2.jpg'),
    File('/path/to/image3.jpg'),
  ];
  
  final result = await Luban.compressBatch(files);
  
  if (result.isSuccess) {
    final batchResult = result.value;
    print('批量压缩完成');
    print('总数: ${batchResult.total}');
    print('成功: ${batchResult.successCount}');
    print('失败: ${batchResult.failureCount}');
    
    for (final item in batchResult.items) {
      if (item.isSuccess) {
        final compressionResult = item.result.value;
        print('${item.originalPath}: ${compressionResult.compressedSizeKb} KB');
      } else {
        print('${item.originalPath}: 压缩失败 - ${item.result.error}');
      }
    }
  } else {
    print('批量压缩失败: ${result.error}');
  }
}

使用路径列表

import 'package:luban/luban.dart';

Future<void> compressBatchImages() async {
  final paths = [
    '/path/to/image1.jpg',
    '/path/to/image2.jpg',
    '/path/to/image3.jpg',
  ];
  
  final result = await Luban.compressBatchPaths(paths);
  
  result.fold(
    (error) => print('批量压缩失败: $error'),
    (batchResult) {
      print('批量压缩完成,成功 ${batchResult.successCount}/${batchResult.total} 张');
      
      for (final compressionResult in batchResult.successfulResults) {
        print('${compressionResult.file.path}: ${compressionResult.compressedSizeKb} KB');
      }
    },
  );
}

批量压缩并指定输出目录

import 'dart:io';
import 'package:luban/luban.dart';

Future<void> compressBatchImages() async {
  final files = [
    File('/path/to/image1.jpg'),
    File('/path/to/image2.jpg'),
  ];
  final outputDir = Directory('/path/to/output');
  
  final result = await Luban.compressBatch(files, outputDir: outputDir);
  
  if (result.isSuccess) {
    final batchResult = result.value;
    print('批量压缩完成,成功 ${batchResult.successCount} 张');
    
    for (final compressionResult in batchResult.successfulResults) {
      print('压缩文件: ${compressionResult.file.path}');
    }
  } else {
    print('批量压缩失败: ${result.error}');
  }
}