Flutter图片合并插件image_combine的使用

Flutter图片合并插件image_combine的使用

image_combine 是一个用于通过Rust原生代码垂直合并图像的Flutter FFI插件。

特性

  • 使用Rust原生代码快速合并图像

使用方法

import 'package:image_combine/image_combine.dart';

void main() async {
  // 初始化插件
  await ImageCombine.instance.initialize();

  // 垂直合并图像
  final result = await ImageCombine.instance.mergeImagesVertically(
    imageBuffers: [image1Bytes, image2Bytes, image3Bytes],
    maxSizeKb: BigInt.from(2048),
  );
}

性能基准测试

测试条件

  • 三个分辨率为3024×4032的图像
  • 每个图像大小约为2MB
  • 未指定最大尺寸
  • 在性能模式下进行测试

测试结果

设备 处理时间
Pixel 8 Pro 约900毫秒
iPhone 15 Pro Max 约500毫秒
iPad Pro M4 约400毫秒
Macbook Pro M1 Max 约500毫秒

已测试平台

  • ✅ Android
  • ✅ iOS
  • ✅ MacOS

完整示例Demo

以下是一个完整的示例代码,展示了如何使用 image_combine 插件来准备和合并图像。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_combine/image_combine.dart';
import 'package:image/image.dart' as img;
import 'package:flutter/foundation.dart';

Future<void> main() async {
  await ImageCombine.instance.initialize();
  runApp(
    const MaterialApp(
      home: MyApp(),
    ),
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool isLoading = false;
  Uint8List? result;
  int? mergeTime;
  List<Uint8List>? preparedImages;
  List<img.Image?>? decodedImages;

  Future<void> _prepareImages() async {
    setState(() => isLoading = true);
    try {
      final images = await _prepareImagesInBackground();
      setState(() {
        preparedImages = images;
        decodedImages = images.map((image) => img.decodeImage(image)).toList();
        isLoading = false;
      });
    } catch (e) {
      _handleError('Error preparing images: $e');
    }
  }

  static Future<List<Uint8List>> _prepareImagesInBackground() async {
    final assetPaths = [
      'assets/demo/receipt_1.jpeg',
      'assets/demo/receipt_1.1.jpeg',
      'assets/demo/receipt_2.jpeg',
    ];

    // 加载所有图像
    final images = await Future.wait(
      assetPaths.map((path) async {
        final data = await rootBundle.load(path);
        return data.buffer.asUint8List();
      }),
    );
    return images;
  }

  Future<void> _mergeImages() async {
    setState(() => isLoading = true);
    try {
      final stopwatch = Stopwatch()..start();
      final mergedResult =
          await compute(_mergeImagesInBackground, preparedImages!);
      stopwatch.stop();

      if (mergedResult == null) {
        throw Exception('Failed to merge images');
      }

      setState(() {
        result = mergedResult;
        isLoading = false;
        mergeTime = stopwatch.elapsedMilliseconds;
      });
    } catch (e) {
      _handleError('Error merging images: $e');
    }
  }

  static Future<Uint8List?> _mergeImagesInBackground(
      List<Uint8List> images) async {
    await ImageCombine.instance.initialize();
    return ImageCombine.instance.mergeImagesVertically(
      imageBuffers: images,
      maxSizeKb: BigInt.from(2048),
    );
  }

  void _handleError(String message) {
    setState(() => isLoading = false);
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Image Merger')),
      body: Builder(
        builder: (context) {
          return SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                _buildPrepareButton(),
                if (preparedImages != null) _buildImagePreview(),
                if (preparedImages != null && result == null)
                  _buildMergeButton(),
                if (mergeTime != null) _buildMergeTimeInfo(),
                if (result != null) Image.memory(result!),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildPrepareButton() {
    return TextButton(
      onPressed: preparedImages != null || isLoading ? null : _prepareImages,
      child: isLoading && preparedImages == null
          ? const CircularProgressIndicator()
          : const Text('Prepare Images'),
    );
  }

  Widget _buildMergeButton() {
    return TextButton(
      onPressed: isLoading ? null : _mergeImages,
      child: isLoading
          ? const CircularProgressIndicator()
          : const Text('Merge Images'),
    );
  }

  Widget _buildImagePreview() {
    return SizedBox(
      height: 200,
      child: SingleChildScrollView(
        scrollDirection: Axis.horizontal,
        child: Row(
          children: [
            for (var i = 0; i < preparedImages!.length; i++)
              _buildImageThumbnail(i),
          ],
        ),
      ),
    );
  }

  Widget _buildImageThumbnail(int index) {
    final decodedImage = decodedImages![index];
    final fileSizeInKB =
        (preparedImages![index].lengthInBytes / (1024)).toStringAsFixed(2);
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          SizedBox(
            width: 100,
            height: 150,
            child: Image.memory(
              preparedImages![index],
              fit: BoxFit.contain,
              cacheHeight: 150,
              cacheWidth: 100,
              // 防止在重建期间闪烁
              gaplessPlayback: true,
            ),
          ),
          Text(
            'Size: ${decodedImage?.width}x${decodedImage?.height}\n'
            'File: $fileSizeInKB KB',
            style: const TextStyle(fontSize: 12),
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }

  Widget _buildMergeTimeInfo() {
    return Column(
      children: [
        Text('Merge time: $mergeTime ms, profile mode will be faster'),
        if (result != null)
          Text('Result size: ${result!.lengthInBytes / (1024)} KB'),
      ],
    );
  }
}

更多关于Flutter图片合并插件image_combine的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter图片合并插件image_combine的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用image_combine插件来合并图片的示例代码。这个插件允许你将多张图片合并成一张图片。以下是一个简单的示例,展示了如何安装和使用这个插件。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加image_combine依赖:

dependencies:
  flutter:
    sdk: flutter
  image_combine: ^x.y.z  # 请将x.y.z替换为最新版本号

然后运行flutter pub get来获取依赖。

2. 导入包

在你的Dart文件中导入image_combine包:

import 'package:image_combine/image_combine.dart';
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:flutter/material.dart';

3. 使用ImageCombine合并图片

下面是一个完整的示例,展示了如何使用ImageCombine将两张图片合并成一张:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Image Combine Example'),
        ),
        body: Center(
          child: ImageCombineExample(),
        ),
      ),
    );
  }
}

class ImageCombineExample extends StatefulWidget {
  @override
  _ImageCombineExampleState createState() => _ImageCombineExampleState();
}

class _ImageCombineExampleState extends State<ImageCombineExample> {
  Uint8List? combinedImageBytes;

  @override
  void initState() {
    super.initState();
    _combineImages();
  }

  Future<void> _combineImages() async {
    // 加载图片资源,这里使用网络图片作为示例
    final image1 = NetworkImage('https://example.com/image1.png');
    final image2 = NetworkImage('https://example.com/image2.png');

    final completer1 = Completer<ui.Image>();
    final completer2 = Completer<ui.Image>();

    image1.image.resolve(const ImageConfiguration()).addListener(
      ImageStreamListener((ImageInfo imageInfo, bool synchronousCall) {
        final ui.Image img = imageInfo.image;
        completer1.complete(img);
      }),
    );

    image2.image.resolve(const ImageConfiguration()).addListener(
      ImageStreamListener((ImageInfo imageInfo, bool synchronousCall) {
        final ui.Image img = imageInfo.image;
        completer2.complete(img);
      }),
    );

    await Future.wait([completer1.future, completer2.future]);

    final img1 = completer1.future.result!;
    final img2 = completer2.future.result!;

    // 创建ImageCombine实例
    final imageCombine = ImageCombine();

    // 将图片转换为ByteData
    final byteData1 = await img1.toByteData(format: ui.ImageByteFormat.png);
    final byteData2 = await img2.toByteData(format: ui.ImageByteFormat.png);

    // 将ByteData转换为Uint8List
    final Uint8List imageBytes1 = byteData1!.buffer.asUint8List();
    final Uint8List imageBytes2 = byteData2!.buffer.asUint8List();

    // 添加图片到ImageCombine
    imageCombine.addImage(imageBytes1);
    imageCombine.addImage(imageBytes2, position: Offset(img1.width, 0)); // 将第二张图片放在第一张图片的右侧

    // 获取合并后的图片
    combinedImageBytes = imageCombine.combine();

    // 更新UI
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        if (combinedImageBytes != null)
          Image.memory(combinedImageBytes!),
        Text('Loading images...'),
      ],
    );
  }
}

注意事项

  1. 图片资源:上面的示例使用了网络图片。如果你使用本地图片资源,可以使用AssetImage代替NetworkImage
  2. 图片位置:在imageCombine.addImage方法中,你可以通过position参数来设置图片的位置。
  3. 图片格式:在将ui.Image转换为ByteData时,你可以根据需要选择图片格式(如PNG或JPEG)。

这个示例展示了如何使用image_combine插件将多张图片合并成一张图片,并在Flutter应用中显示合并后的图片。希望这对你有所帮助!

回到顶部