1、新建项目 (streaming_writing_to_png)
js
flutter create --org com.company.streamingwritingtopng --template=plugin_ffi --platforms=android,ios streaming_writing_to_png
2、将代码写到src目录下
当我们执行完第一步的命令之后会生成一个项目,里面有1个src文件夹,里面是需要输入我的的C语言代码的.
例子
我的目录结构
css
src/
├── libspng/
│ ├── spng.h
│ └── spng.c
├── streaming_writing_to_png.h
└── streaming_writing_to_png.c
js
// src/streaming_writing_to_png.h
#include <stdint.h>
#if _WIN32
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
#else
#define FFI_PLUGIN_EXPORT
#endif
FFI_PLUGIN_EXPORT int32_t start_png_export(const char* file_path, uint32_t width, uint32_t height);
FFI_PLUGIN_EXPORT int32_t write_png_chunk(uint8_t* chunk_data);
FFI_PLUGIN_EXPORT int32_t finish_png_export(void);
js
// src/streaming_writing_to_png.c
#include "streaming_writing_to_png.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libspng/spng.h>
typedef struct PNGStreamWriter {
// code...
} PNGStreamWriter;
PNGStreamWriter* writer = NULL;
// 释放资源
void png_stream_writer_free(PNGStreamWriter* writer) {
// code...
}
// 写入一行像素数据 (RGBA格式,每像素4字节)
int png_stream_writer_write_row(PNGStreamWriter* writer, const uint8_t* row_data) {
// code...
}
// 完成编码(验证是否写入了所有行)
int png_stream_writer_finish(PNGStreamWriter* writer) {
// code...
}
// 静态回调函数用于写入数据
static int write_callback(spng_ctx* ctx, void* user, void* data, size_t length) {
// code...
}
// 创建 PNGStreamWriter 对象
PNGStreamWriter* png_stream_writer_new(const char* filename, uint32_t width, uint32_t height) {
// code...
}
// 获取当前行号
uint32_t png_stream_writer_get_current_row(PNGStreamWriter* writer) {
// code...
}
// 获取总行数
uint32_t png_stream_writer_get_total_rows(PNGStreamWriter* writer) {
// code...
}
// 便捷函数:写入行数据(带长度检查)
int png_stream_writer_write_row_checked(PNGStreamWriter* writer, const uint8_t* row_data, size_t data_size) {
// code...
}
// -------------------------------------------------------------
// FFI 接口 1: 初始化(开始 PNG 编码)
// -------------------------------------------------------------
int32_t start_png_export(const char* file_path, uint32_t width, uint32_t height) {
// code...
}
// -------------------------------------------------------------
// FFI 接口 2: 写入数据块
// -------------------------------------------------------------
// 接收当前数据块的指针和字节大小
int32_t write_png_chunk(uint8_t* chunk_data) {
// code...
}
// -------------------------------------------------------------
// FFI 接口 3: 完成(写入 PNG 尾部和清理)
// -------------------------------------------------------------
int32_t finish_png_export(void) {
// code...
}
3、生成函数绑定
js
dart run ffigen --config ffigen.yaml
执行完上面语句之后就生成绑定到lib/streaming_writing_to_png_bindings_generated.dart
,需要提前执行flutter pub add ffigen
4、测试
到 example/lib/main.dart
编写测试程序进行测试。
5、会遇到的问题
我们使用到了一个libspng的开源库,因此我们要做以下处理:
5.1 在 ios/Classes/streaming_writing_to_png.c``#include "../../src/libspng/spng.c"
5.2 修改 ios/streaming_writing_to_png.podspec
文件
js
Pod::Spec.new do |s|
s.name = 'streaming_writing_to_png'
s.version = '0.0.1'
s.summary = 'A new Flutter FFI plugin project.'
s.description = <<-DESC
A new Flutter FFI plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
# This will ensure the source files in Classes/ are included in the native
# builds of apps using this FFI plugin. Podspec does not support relative
# paths, so Classes contains a forwarder C file that relatively imports
# `../src/*` so that the C sources can be shared among all target platforms.
s.source = { :path => '.' }
# 新增了'../src/**/*.{h,c}'
s.source_files = 'Classes/**/*', '../src/**/*.{h,c}'
# 本行为新增
s.public_header_files = '../src/**/*.h'
s.dependency 'Flutter'
s.platform = :ios, '11.0'
s.libraries = 'z' # 本行为新增,添加zip依赖
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = {
# 本行为新增
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}/../src"',
'DEFINES_MODULE' => 'YES',
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
# 本行为新增
'GCC_PREPROCESSOR_DEFINITIONS' => 'SPNG_STATIC=1'
}
s.swift_version = '5.0'
end