Merge pull request #1 from koji-1009/feat/convert
feat: Add ImageConverter.convert
This commit is contained in:
commit
9c8fc0c45e
179
README.md
179
README.md
|
|
@ -1,92 +1,139 @@
|
||||||
# image_ffi
|
# image_ffi
|
||||||
|
|
||||||
A new Flutter FFI plugin project.
|
A high-performance Flutter plugin for cross-platform image format conversion using native APIs.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🖼️ **Versatile Format Conversion**: Supports conversion between JPEG, PNG, and WebP. It also handles HEIC/HEIF, allowing conversion *from* HEIC on all supported platforms and *to* HEIC on iOS/macOS.
|
||||||
|
- ⚡ **Native Performance**: Achieves high speed by using platform-native APIs directly: ImageIO on iOS/macOS and BitmapFactory on Android.
|
||||||
|
- 🔒 **Efficient Native Interop**: Employs FFI and JNI to create a fast, type-safe bridge between Dart and native code, ensuring robust and reliable communication.
|
||||||
|
|
||||||
|
## Platform Support
|
||||||
|
|
||||||
|
| Platform | Minimum Version | API Used |
|
||||||
|
|----------|-----------------|----------|
|
||||||
|
| iOS | 14.0 | ImageIO (CoreFoundation, CoreGraphics) |
|
||||||
|
| macOS | 10.15 | ImageIO (CoreFoundation, CoreGraphics) |
|
||||||
|
| Android | 7 | BitmapFactory, Bitmap compression |
|
||||||
|
| Web | N/A | Not supported |
|
||||||
|
|
||||||
|
on Android, HEIC input is supported on Android 9+ but HEIC output is not supported.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
This project is a starting point for a Flutter
|
### Installation
|
||||||
[FFI plugin](https://flutter.dev/to/ffi-package),
|
|
||||||
a specialized package that includes native code directly invoked with Dart FFI.
|
|
||||||
|
|
||||||
## Project structure
|
Add `image_ffi` to your `pubspec.yaml`:
|
||||||
|
|
||||||
This template uses the following structure:
|
|
||||||
|
|
||||||
* `src`: Contains the native source code, and a CmakeFile.txt file for building
|
|
||||||
that source code into a dynamic library.
|
|
||||||
|
|
||||||
* `lib`: Contains the Dart code that defines the API of the plugin, and which
|
|
||||||
calls into the native code using `dart:ffi`.
|
|
||||||
|
|
||||||
* platform folders (`android`, `ios`, `windows`, etc.): Contains the build files
|
|
||||||
for building and bundling the native code library with the platform application.
|
|
||||||
|
|
||||||
## Building and bundling native code
|
|
||||||
|
|
||||||
The `pubspec.yaml` specifies FFI plugins as follows:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
plugin:
|
dependencies:
|
||||||
platforms:
|
image_ffi: ^0.0.1
|
||||||
some_platform:
|
|
||||||
ffiPlugin: true
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This configuration invokes the native build for the various target platforms
|
### Basic Usage
|
||||||
and bundles the binaries in Flutter applications using these FFI plugins.
|
|
||||||
|
|
||||||
This can be combined with dartPluginClass, such as when FFI is used for the
|
```dart
|
||||||
implementation of one platform in a federated plugin:
|
import 'package:image_ffi/image_ffi.dart';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
```yaml
|
// Convert HEIC image to JPEG
|
||||||
plugin:
|
final jpegData = await ImageConverter.convert(
|
||||||
implements: some_other_plugin
|
inputData: heicImageData,
|
||||||
platforms:
|
format: OutputFormat.jpeg,
|
||||||
some_platform:
|
quality: 90,
|
||||||
dartPluginClass: SomeClass
|
);
|
||||||
ffiPlugin: true
|
|
||||||
|
// Convert any format to PNG
|
||||||
|
final pngData = await ImageConverter.convert(
|
||||||
|
inputData: imageData,
|
||||||
|
format: OutputFormat.png,
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
A plugin can have both FFI and method channels:
|
## Supported Formats
|
||||||
|
|
||||||
```yaml
|
### Input Formats
|
||||||
plugin:
|
- **iOS/macOS**: JPEG, PNG, HEIC, WebP, BMP, GIF, TIFF, and more
|
||||||
platforms:
|
- **Android**: JPEG, PNG, WebP, GIF, BMP, HEIC (via BitmapFactory)
|
||||||
some_platform:
|
|
||||||
pluginClass: SomeName
|
### Output Formats
|
||||||
ffiPlugin: true
|
The supported output formats are defined by the `OutputFormat` enum, with platform-specific limitations:
|
||||||
|
- **JPEG**: Supported on all platforms.
|
||||||
|
- **PNG**: Supported on all platforms.
|
||||||
|
- **WebP**: Supported on Android only.
|
||||||
|
- **HEIC**: Supported on iOS/macOS only.
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### `ImageConverter.convert()`
|
||||||
|
|
||||||
|
```dart
|
||||||
|
static Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async
|
||||||
```
|
```
|
||||||
|
|
||||||
The native build systems that are invoked by FFI (and method channel) plugins are:
|
**Parameters:**
|
||||||
|
- `inputData` (`Uint8List`): Raw image data to convert
|
||||||
|
- `format` (`OutputFormat`): Target image format (default: JPEG)
|
||||||
|
- `quality` (`int`): Compression quality 1-100 (default: 100, only for lossy formats)
|
||||||
|
|
||||||
* For Android: Gradle, which invokes the Android NDK for native builds.
|
**Returns:** `Future<Uint8List>` containing the converted image data
|
||||||
* See the documentation in android/build.gradle.
|
|
||||||
* For iOS and MacOS: Xcode, via CocoaPods.
|
|
||||||
* See the documentation in ios/image_ffi.podspec.
|
|
||||||
* See the documentation in macos/image_ffi.podspec.
|
|
||||||
* For Linux and Windows: CMake.
|
|
||||||
* See the documentation in linux/CMakeLists.txt.
|
|
||||||
* See the documentation in windows/CMakeLists.txt.
|
|
||||||
|
|
||||||
## Binding to native code
|
**Throws:**
|
||||||
|
- `UnsupportedError`: If the platform or format is not supported
|
||||||
|
- `Exception`: If conversion fails
|
||||||
|
|
||||||
To use the native code, bindings in Dart are needed.
|
### `OutputFormat` Enum
|
||||||
To avoid writing these by hand, they are generated from the header file
|
|
||||||
(`src/image_ffi.h`) by `package:ffigen`.
|
|
||||||
Regenerate the bindings by running `dart run ffigen --config ffigen.yaml`.
|
|
||||||
|
|
||||||
## Invoking native code
|
```dart
|
||||||
|
enum OutputFormat {
|
||||||
|
/// JPEG format (.jpg, .jpeg)
|
||||||
|
/// Lossy compression, suitable for photos
|
||||||
|
jpeg,
|
||||||
|
|
||||||
Very short-running native functions can be directly invoked from any isolate.
|
/// PNG format (.png)
|
||||||
For example, see `sum` in `lib/image_ffi.dart`.
|
/// Lossless compression, supports transparency
|
||||||
|
png,
|
||||||
|
|
||||||
Longer-running functions should be invoked on a helper isolate to avoid
|
/// WebP format (.webp)
|
||||||
dropping frames in Flutter applications.
|
/// Modern format with superior compression (not supported on Darwin)
|
||||||
For example, see `sumAsync` in `lib/image_ffi.dart`.
|
webp,
|
||||||
|
|
||||||
## Flutter help
|
/// HEIC format (.heic)
|
||||||
|
/// High Efficiency Image Format (not supported on Android)
|
||||||
|
heic,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
For help getting started with Flutter, view our
|
## Implementation Details
|
||||||
[online documentation](https://docs.flutter.dev), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
|
||||||
|
|
||||||
|
### iOS/macOS Implementation
|
||||||
|
|
||||||
|
The iOS/macOS implementation uses the [ImageIO](https://developer.apple.com/documentation/imageio) framework via FFI bindings:
|
||||||
|
|
||||||
|
1. **Decoding**: `CGImageSourceCreateWithData` reads input data
|
||||||
|
2. **Rendering**: `CGImageSourceCreateImageAtIndex` decodes to `CGImage`
|
||||||
|
3. **Encoding**: `CGImageDestinationCreateWithData` encodes to target format
|
||||||
|
4. **Quality**: Uses `kCGImageDestinationLossyCompressionQuality` for JPEG/WebP
|
||||||
|
|
||||||
|
**Key Functions:**
|
||||||
|
- `CFDataCreate`: Create immutable data from input bytes
|
||||||
|
- `CGImageSourceCreateWithData`: Create image source from data
|
||||||
|
- `CGImageDestinationCreateWithData`: Create image destination
|
||||||
|
- `CGImageDestinationAddImage`: Add image to destination
|
||||||
|
- `CGImageDestinationFinalize`: Complete encoding
|
||||||
|
|
||||||
|
### Android Implementation
|
||||||
|
|
||||||
|
The Android implementation uses [BitmapFactory](https://developer.android.com/reference/android/graphics/BitmapFactory) and [Bitmap.compress](https://developer.android.com/reference/android/graphics/Bitmap#compress(android.graphics.Bitmap.CompressFormat,%20int,%20java.io.OutputStream)):
|
||||||
|
|
||||||
|
1. **Decoding**: `BitmapFactory.decodeByteArray` handles all supported formats
|
||||||
|
2. **Compression**: `Bitmap.compress` encodes to target format
|
||||||
|
3. **Buffer Management**: `ByteArrayOutputStream` manages output data
|
||||||
|
|
||||||
|
**Key Limitations:**
|
||||||
|
- HEIC can be read (input only) but cannot be written (output format not supported)
|
||||||
|
- Requires Android 9+ for full HEIC support
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
*.iml
|
|
||||||
.gradle
|
|
||||||
/local.properties
|
|
||||||
/.idea/workspace.xml
|
|
||||||
/.idea/libraries
|
|
||||||
.DS_Store
|
|
||||||
/build
|
|
||||||
/captures
|
|
||||||
.cxx
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
// The Android Gradle Plugin builds the native code with the Android NDK.
|
|
||||||
|
|
||||||
group = "dr1009.com.image_ffi"
|
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
// The Android Gradle Plugin knows how to build native code with the NDK.
|
|
||||||
classpath("com.android.tools.build:gradle:8.11.1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.allprojects {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: "com.android.library"
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "dr1009.com.image_ffi"
|
|
||||||
|
|
||||||
// Bumping the plugin compileSdk version requires all clients of this plugin
|
|
||||||
// to bump the version in their app.
|
|
||||||
compileSdk = 36
|
|
||||||
|
|
||||||
// Use the NDK version
|
|
||||||
// declared in /android/app/build.gradle file of the Flutter project.
|
|
||||||
// Replace it with a version number if this plugin requires a specific NDK version.
|
|
||||||
// (e.g. ndkVersion "23.1.7779620")
|
|
||||||
ndkVersion = android.ndkVersion
|
|
||||||
|
|
||||||
// Invoke the shared CMake build with the Android Gradle Plugin.
|
|
||||||
externalNativeBuild {
|
|
||||||
cmake {
|
|
||||||
path = "../src/CMakeLists.txt"
|
|
||||||
|
|
||||||
// The default CMake version for the Android Gradle Plugin is 3.10.2.
|
|
||||||
// https://developer.android.com/studio/projects/install-ndk#vanilla_cmake
|
|
||||||
//
|
|
||||||
// The Flutter tooling requires that developers have CMake 3.10 or later
|
|
||||||
// installed. You should not increase this version, as doing so will cause
|
|
||||||
// the plugin to fail to compile for some customers of the plugin.
|
|
||||||
// version "3.10.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
rootProject.name = 'image_ffi'
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="dr1009.com.image_ffi">
|
|
||||||
</manifest>
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|
@ -55,6 +56,7 @@
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
|
@ -62,6 +64,7 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -79,6 +82,7 @@
|
||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
|
@ -142,6 +146,9 @@
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
packageProductDependencies = (
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||||
|
);
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
|
@ -165,6 +172,9 @@
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
/* Begin PBXProject section */
|
||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
packageReferences = (
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||||
|
);
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
|
@ -611,6 +621,18 @@
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
/* End XCConfigurationList section */
|
/* End XCConfigurationList section */
|
||||||
|
/* Begin XCLocalSwiftPackageReference section */
|
||||||
|
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||||
|
isa = XCLocalSwiftPackageReference;
|
||||||
|
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCLocalSwiftPackageReference section */
|
||||||
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
|
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
productName = FlutterGeneratedPluginSwiftPackage;
|
||||||
|
};
|
||||||
|
/* End XCSwiftPackageProductDependency section */
|
||||||
};
|
};
|
||||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,24 @@
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
|
<PreActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Prepare Flutter Framework Script"
|
||||||
|
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PreActions>
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
||||||
|
|
@ -1,74 +1,144 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'dart:typed_data';
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:image_ffi/image_ffi.dart' as image_ffi;
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image_ffi/image_ffi.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(const MainApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class MainApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MainApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MyApp> createState() => _MyAppState();
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Image FFI Demo',
|
||||||
|
theme: ThemeData.light(),
|
||||||
|
home: MainPage(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class MainPage extends StatefulWidget {
|
||||||
late int sumResult;
|
const MainPage({super.key});
|
||||||
late Future<int> sumAsyncResult;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
State<MainPage> createState() => _MainPageState();
|
||||||
super.initState();
|
}
|
||||||
sumResult = image_ffi.sum(1, 2);
|
|
||||||
sumAsyncResult = image_ffi.sumAsync(3, 4);
|
class _MainPageState extends State<MainPage> {
|
||||||
|
Uint8List? _originalImage;
|
||||||
|
String? _originalName;
|
||||||
|
Uint8List? _convertedImage;
|
||||||
|
String? _convertedFormat;
|
||||||
|
int? _convertElapsedMs;
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
Future<void> _pickImage() async {
|
||||||
|
final picker = ImagePicker();
|
||||||
|
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (pickedFile != null) {
|
||||||
|
_originalImage = await pickedFile.readAsBytes();
|
||||||
|
_originalName = pickedFile.name;
|
||||||
|
_convertedImage = null;
|
||||||
|
_convertedFormat = null;
|
||||||
|
_convertElapsedMs = null;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _convertImage(OutputFormat format) async {
|
||||||
|
if (_originalImage == null) return;
|
||||||
|
setState(() => _isLoading = true);
|
||||||
|
final sw = Stopwatch()..start();
|
||||||
|
try {
|
||||||
|
final converted = await ImageConverter.convert(
|
||||||
|
inputData: _originalImage!,
|
||||||
|
format: format,
|
||||||
|
quality: 90,
|
||||||
|
);
|
||||||
|
sw.stop();
|
||||||
|
_convertedImage = converted;
|
||||||
|
_convertedFormat = format.name;
|
||||||
|
_convertElapsedMs = sw.elapsedMilliseconds;
|
||||||
|
setState(() {});
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(
|
||||||
|
context,
|
||||||
|
).showSnackBar(SnackBar(content: Text('Conversion failed: $e')));
|
||||||
|
} finally {
|
||||||
|
setState(() => _isLoading = false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const textStyle = TextStyle(fontSize: 25);
|
return Scaffold(
|
||||||
const spacerSmall = SizedBox(height: 10);
|
appBar: AppBar(title: const Text('image_ffi Demo')),
|
||||||
return MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Native Packages'),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Container(
|
padding: const .all(16),
|
||||||
padding: const .all(10),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const Text(
|
FilledButton(
|
||||||
'This calls a native function through FFI that is shipped as source in the package. '
|
onPressed: _pickImage,
|
||||||
'The native code is built as part of the Flutter Runner build.',
|
child: const Text('Pick Image'),
|
||||||
style: textStyle,
|
|
||||||
textAlign: .center,
|
|
||||||
),
|
),
|
||||||
spacerSmall,
|
if (_originalImage != null) ...[
|
||||||
Text(
|
Text('Original Image ($_originalName): '),
|
||||||
'sum(1, 2) = $sumResult',
|
Image.memory(_originalImage!, height: 180),
|
||||||
style: textStyle,
|
const SizedBox(height: 8),
|
||||||
textAlign: .center,
|
Row(
|
||||||
|
spacing: 4,
|
||||||
|
children: [
|
||||||
|
ActionChip(
|
||||||
|
onPressed: _isLoading
|
||||||
|
? null
|
||||||
|
: () => _convertImage(OutputFormat.jpeg),
|
||||||
|
label: const Text('to JPG'),
|
||||||
),
|
),
|
||||||
spacerSmall,
|
ActionChip(
|
||||||
FutureBuilder<int>(
|
onPressed: _isLoading
|
||||||
future: sumAsyncResult,
|
? null
|
||||||
builder: (BuildContext context, AsyncSnapshot<int> value) {
|
: () => _convertImage(OutputFormat.png),
|
||||||
final displayValue =
|
label: const Text('to PNG'),
|
||||||
(value.hasData) ? value.data : 'loading';
|
),
|
||||||
return Text(
|
ActionChip(
|
||||||
'await sumAsync(3, 4) = $displayValue',
|
onPressed: _isLoading
|
||||||
style: textStyle,
|
? null
|
||||||
textAlign: .center,
|
: () => _convertImage(OutputFormat.webp),
|
||||||
);
|
label: const Text('to WebP'),
|
||||||
},
|
),
|
||||||
|
ActionChip(
|
||||||
|
onPressed: _isLoading
|
||||||
|
? null
|
||||||
|
: () => _convertImage(OutputFormat.heic),
|
||||||
|
label: const Text('to HEIC'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (_isLoading)
|
||||||
|
const Padding(
|
||||||
|
padding: .all(16),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
if (!_isLoading && _convertedImage != null)
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text('Converted ($_convertedFormat):'),
|
||||||
|
Image.memory(_convertedImage!, height: 180),
|
||||||
|
Text('Size: ${_convertedImage!.lengthInBytes} bytes'),
|
||||||
|
if (_convertElapsedMs != null)
|
||||||
|
Text('Convert time: $_convertElapsedMs ms'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -33,6 +41,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.2"
|
||||||
|
code_assets:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_assets
|
||||||
|
sha256: ae0db647e668cbb295a3527f0938e4039e004c80099dce2f964102373f5ce0b5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.19.10"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -41,14 +57,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
cupertino_icons:
|
cross_file:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cross_file
|
||||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "0.3.5+1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.7"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -57,6 +81,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.3"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.4"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.5"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+5"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -70,11 +142,56 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.33"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
hooks:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hooks
|
||||||
|
sha256: "5410b9f4f6c9f01e8ff0eb81c9801ea13a3c3d39f8f0b1613cda08e27eab3c18"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.20.5"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.2"
|
||||||
image_ffi:
|
image_ffi:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -82,6 +199,78 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: "5e9bf126c37c117cf8094215373c6d561117a3cfb50ebc5add1a61dc6e224677"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.13+10"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: "956c16a42c0c708f914021666ffcd8265dde36e673c9fa68c81f7d085d9774ad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.13+3"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2+1"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.11.1"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
|
jni:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: jni
|
||||||
|
sha256: "8706a77e94c76fe9ec9315e18949cc9479cc03af97085ca9c1077b61323ea12d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.2"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -114,6 +303,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -138,6 +335,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.0"
|
version: "1.17.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
native_toolchain_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: native_toolchain_c
|
||||||
|
sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.2"
|
||||||
|
objective_c:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: objective_c
|
||||||
|
sha256: c12701c978eacda8d8a9be1469f005e26bf16a903945982b94f77d7a9dbeea45
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.2.1"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -154,6 +383,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -207,6 +444,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.7"
|
version: "0.7.7"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -223,6 +468,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "15.0.2"
|
version: "15.0.2"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.0 <4.0.0"
|
dart: ">=3.10.0 <4.0.0"
|
||||||
flutter: ">=3.35.0"
|
flutter: ">=3.35.6"
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,24 @@
|
||||||
name: image_ffi_example
|
name: image_ffi_example
|
||||||
description: "Demonstrates how to use the image_ffi plugin."
|
description: "Demonstrates how to use the image_ffi plugin."
|
||||||
# The following line prevents the package from being accidentally published to
|
publish_to: 'none'
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.0
|
sdk: ^3.10.0
|
||||||
flutter: '>=3.35.0'
|
flutter: '>=3.35.0'
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
image_ffi:
|
image_ffi:
|
||||||
# When depending on this package from a real application you should use:
|
|
||||||
# image_ffi: ^x.y.z
|
|
||||||
# See https://dart.dev/tools/pub/dependencies#version-constraints
|
|
||||||
# The example app is bundled with the plugin so we use a path dependency on
|
|
||||||
# the parent directory to use the current plugin's version.
|
|
||||||
path: ../
|
path: ../
|
||||||
|
image_picker:
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.8
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/to/asset-from-package
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/to/font-from-package
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Regenerate bindings with `dart run ffigen.dart`.
|
||||||
|
import 'package:ffigen/ffigen.dart';
|
||||||
|
|
||||||
|
final config = FfiGenerator(
|
||||||
|
headers: Headers(
|
||||||
|
entryPoints: [
|
||||||
|
Uri.file(
|
||||||
|
'$macSdkPath/System/Library/Frameworks/ImageIO.framework/Headers/ImageIO.h',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
objectiveC: ObjectiveC(interfaces: Interfaces.includeSet({'ImageIO'})),
|
||||||
|
output: Output(dartFile: Uri.file('lib/gen/darwin_bindings.dart')),
|
||||||
|
functions: Functions.includeSet({
|
||||||
|
// CFData operations
|
||||||
|
'CFDataCreate',
|
||||||
|
'CFDataCreateMutable',
|
||||||
|
'CFDataGetBytePtr',
|
||||||
|
'CFDataGetLength',
|
||||||
|
// CGImageSource operations (decoding)
|
||||||
|
'CGImageSourceCreateWithData',
|
||||||
|
'CGImageSourceCreateImageAtIndex',
|
||||||
|
// CGImageDestination operations (encoding)
|
||||||
|
'CGImageDestinationCreateWithData',
|
||||||
|
'CGImageDestinationAddImage',
|
||||||
|
'CGImageDestinationFinalize',
|
||||||
|
}),
|
||||||
|
globals: Globals.includeSet({'kCFAllocatorDefault'}),
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() => config.generate();
|
||||||
19
ffigen.yaml
19
ffigen.yaml
|
|
@ -1,19 +0,0 @@
|
||||||
# Run with `dart run ffigen --config ffigen.yaml`.
|
|
||||||
name: ImageFfiBindings
|
|
||||||
description: |
|
|
||||||
Bindings for `src/image_ffi.h`.
|
|
||||||
|
|
||||||
Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
|
|
||||||
output: 'lib/image_ffi_bindings_generated.dart'
|
|
||||||
headers:
|
|
||||||
entry-points:
|
|
||||||
- 'src/image_ffi.h'
|
|
||||||
include-directives:
|
|
||||||
- 'src/image_ffi.h'
|
|
||||||
preamble: |
|
|
||||||
// ignore_for_file: always_specify_types
|
|
||||||
// ignore_for_file: camel_case_types
|
|
||||||
// ignore_for_file: non_constant_identifier_names
|
|
||||||
comments:
|
|
||||||
style: any
|
|
||||||
length: full
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
// Relative import to be able to reuse the C sources.
|
|
||||||
// See the comment in ../image_ffi.podspec for more information.
|
|
||||||
#include "../../src/image_ffi.c"
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#
|
|
||||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
|
||||||
# Run `pod lib lint image_ffi.podspec` to validate before publishing.
|
|
||||||
#
|
|
||||||
Pod::Spec.new do |s|
|
|
||||||
s.name = 'image_ffi'
|
|
||||||
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 => '.' }
|
|
||||||
s.source_files = 'Classes/**/*'
|
|
||||||
s.dependency 'Flutter'
|
|
||||||
s.platform = :ios, '13.0'
|
|
||||||
|
|
||||||
# Flutter.framework does not contain a i386 slice.
|
|
||||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
|
|
||||||
s.swift_version = '5.0'
|
|
||||||
end
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Regenerate bindings with `dart run jnigen --config jnigen.yaml`.
|
||||||
|
|
||||||
|
android_sdk_config:
|
||||||
|
add_gradle_deps: true
|
||||||
|
android_example: 'example/'
|
||||||
|
|
||||||
|
output:
|
||||||
|
dart:
|
||||||
|
path: lib/gen/jnigen_bindings.dart
|
||||||
|
structure: single_file
|
||||||
|
|
||||||
|
source_path:
|
||||||
|
- 'java/'
|
||||||
|
classes:
|
||||||
|
- 'java.io.ByteArrayOutputStream'
|
||||||
|
- 'android/graphics/BitmapFactory'
|
||||||
|
- 'android/graphics/Bitmap'
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
// AUTO GENERATED FILE, DO NOT EDIT.
|
||||||
|
//
|
||||||
|
// Generated by `package:ffigen`.
|
||||||
|
// ignore_for_file: type=lint, unused_import
|
||||||
|
import 'dart:ffi' as ffi;
|
||||||
|
import 'package:objective_c/objective_c.dart' as objc;
|
||||||
|
|
||||||
|
@ffi.Native<ffi.Pointer<__CFAllocator>>()
|
||||||
|
external final ffi.Pointer<__CFAllocator> kCFAllocatorDefault;
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Pointer<__CFData> Function(
|
||||||
|
ffi.Pointer<__CFAllocator>,
|
||||||
|
ffi.Pointer<ffi.UnsignedChar>,
|
||||||
|
ffi.Long,
|
||||||
|
)
|
||||||
|
>()
|
||||||
|
external ffi.Pointer<__CFData> CFDataCreate(
|
||||||
|
ffi.Pointer<__CFAllocator> allocator,
|
||||||
|
ffi.Pointer<ffi.UnsignedChar> bytes,
|
||||||
|
int length,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Pointer<__CFData> Function(ffi.Pointer<__CFAllocator>, ffi.Long)
|
||||||
|
>()
|
||||||
|
external ffi.Pointer<__CFData> CFDataCreateMutable(
|
||||||
|
ffi.Pointer<__CFAllocator> allocator,
|
||||||
|
int capacity,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<ffi.Long Function(ffi.Pointer<__CFData>)>()
|
||||||
|
external int CFDataGetLength(ffi.Pointer<__CFData> theData);
|
||||||
|
|
||||||
|
@ffi.Native<ffi.Pointer<ffi.UnsignedChar> Function(ffi.Pointer<__CFData>)>()
|
||||||
|
external ffi.Pointer<ffi.UnsignedChar> CFDataGetBytePtr(
|
||||||
|
ffi.Pointer<__CFData> theData,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Pointer<CGImageSource> Function(
|
||||||
|
ffi.Pointer<__CFData>,
|
||||||
|
ffi.Pointer<__CFDictionary>,
|
||||||
|
)
|
||||||
|
>()
|
||||||
|
external ffi.Pointer<CGImageSource> CGImageSourceCreateWithData(
|
||||||
|
ffi.Pointer<__CFData> data,
|
||||||
|
ffi.Pointer<__CFDictionary> options,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Pointer<CGImage> Function(
|
||||||
|
ffi.Pointer<CGImageSource>,
|
||||||
|
ffi.Size,
|
||||||
|
ffi.Pointer<__CFDictionary>,
|
||||||
|
)
|
||||||
|
>()
|
||||||
|
external ffi.Pointer<CGImage> CGImageSourceCreateImageAtIndex(
|
||||||
|
ffi.Pointer<CGImageSource> isrc,
|
||||||
|
int index,
|
||||||
|
ffi.Pointer<__CFDictionary> options,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Pointer<CGImageDestination> Function(
|
||||||
|
ffi.Pointer<__CFData>,
|
||||||
|
ffi.Pointer<objc.CFString>,
|
||||||
|
ffi.Size,
|
||||||
|
ffi.Pointer<__CFDictionary>,
|
||||||
|
)
|
||||||
|
>()
|
||||||
|
external ffi.Pointer<CGImageDestination> CGImageDestinationCreateWithData(
|
||||||
|
ffi.Pointer<__CFData> data,
|
||||||
|
ffi.Pointer<objc.CFString> type,
|
||||||
|
int count,
|
||||||
|
ffi.Pointer<__CFDictionary> options,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<
|
||||||
|
ffi.Void Function(
|
||||||
|
ffi.Pointer<CGImageDestination>,
|
||||||
|
ffi.Pointer<CGImage>,
|
||||||
|
ffi.Pointer<__CFDictionary>,
|
||||||
|
)
|
||||||
|
>()
|
||||||
|
external void CGImageDestinationAddImage(
|
||||||
|
ffi.Pointer<CGImageDestination> idst,
|
||||||
|
ffi.Pointer<CGImage> image,
|
||||||
|
ffi.Pointer<__CFDictionary> properties,
|
||||||
|
);
|
||||||
|
|
||||||
|
@ffi.Native<ffi.Bool Function(ffi.Pointer<CGImageDestination>)>()
|
||||||
|
external bool CGImageDestinationFinalize(ffi.Pointer<CGImageDestination> idst);
|
||||||
|
|
||||||
|
final class __CFAllocator extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class __CFDictionary extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class __CFData extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class CGImageSource extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class CGImage extends ffi.Opaque {}
|
||||||
|
|
||||||
|
final class CGImageDestination extends ffi.Opaque {}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,131 +1,121 @@
|
||||||
|
library;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'image_ffi_bindings_generated.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:image_ffi/src/image_converter_android.dart';
|
||||||
|
import 'package:image_ffi/src/image_converter_darwin.dart';
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
|
||||||
/// A very short-lived native function.
|
export 'src/output_format.dart';
|
||||||
|
|
||||||
|
/// Main entry point for image format conversion.
|
||||||
///
|
///
|
||||||
/// For very short-lived functions, it is fine to call them on the main isolate.
|
/// Provides a platform-agnostic interface to convert images across iOS,
|
||||||
/// They will block the Dart execution while running the native function, so
|
/// macOS, and Android platforms using native APIs.
|
||||||
/// only do this for native functions which are guaranteed to be short-lived.
|
class ImageConverter {
|
||||||
int sum(int a, int b) => _bindings.sum(a, b);
|
/// The platform-specific implementation of the image converter.
|
||||||
|
|
||||||
/// A longer lived native function, which occupies the thread calling it.
|
|
||||||
///
|
///
|
||||||
/// Do not call these kind of native functions in the main isolate. They will
|
/// This is initialized based on the current platform.
|
||||||
/// block Dart execution. This will cause dropped frames in Flutter applications.
|
static ImageConverterPlatform get _platform =>
|
||||||
/// Instead, call these native functions on a separate isolate.
|
_getPlatformForTarget(defaultTargetPlatform);
|
||||||
|
|
||||||
|
/// Converts an image to a target format.
|
||||||
///
|
///
|
||||||
/// Modify this to suit your own use case. Example use cases:
|
/// By default, this operation is performed in a separate isolate to avoid
|
||||||
|
/// blocking the UI thread. For very small images, the overhead of an isolate
|
||||||
|
/// can be disabled by setting [runInIsolate] to `false`.
|
||||||
///
|
///
|
||||||
/// 1. Reuse a single isolate for various different kinds of requests.
|
/// **Parameters:**
|
||||||
/// 2. Use multiple helper isolates for parallel execution.
|
/// - [inputData]: Raw bytes of the image to convert.
|
||||||
Future<int> sumAsync(int a, int b) async {
|
/// - [format]: Target [OutputFormat]. Defaults to [OutputFormat.jpeg].
|
||||||
final SendPort helperIsolateSendPort = await _helperIsolateSendPort;
|
/// - [quality]: Compression quality for lossy formats (1-100).
|
||||||
final int requestId = _nextSumRequestId++;
|
/// - [runInIsolate]: Whether to run the conversion in a separate isolate.
|
||||||
final _SumRequest request = _SumRequest(requestId, a, b);
|
/// Defaults to `true`.
|
||||||
final Completer<int> completer = Completer<int>();
|
|
||||||
_sumRequests[requestId] = completer;
|
|
||||||
helperIsolateSendPort.send(request);
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
const String _libName = 'image_ffi';
|
|
||||||
|
|
||||||
/// The dynamic library in which the symbols for [ImageFfiBindings] can be found.
|
|
||||||
final DynamicLibrary _dylib = () {
|
|
||||||
if (Platform.isMacOS || Platform.isIOS) {
|
|
||||||
return DynamicLibrary.open('$_libName.framework/$_libName');
|
|
||||||
}
|
|
||||||
if (Platform.isAndroid || Platform.isLinux) {
|
|
||||||
return DynamicLibrary.open('lib$_libName.so');
|
|
||||||
}
|
|
||||||
if (Platform.isWindows) {
|
|
||||||
return DynamicLibrary.open('$_libName.dll');
|
|
||||||
}
|
|
||||||
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
|
|
||||||
}();
|
|
||||||
|
|
||||||
/// The bindings to the native functions in [_dylib].
|
|
||||||
final ImageFfiBindings _bindings = ImageFfiBindings(_dylib);
|
|
||||||
|
|
||||||
|
|
||||||
/// A request to compute `sum`.
|
|
||||||
///
|
///
|
||||||
/// Typically sent from one isolate to another.
|
/// **Returns:** A [Future] that completes with the converted image data.
|
||||||
class _SumRequest {
|
|
||||||
final int id;
|
|
||||||
final int a;
|
|
||||||
final int b;
|
|
||||||
|
|
||||||
const _SumRequest(this.id, this.a, this.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A response with the result of `sum`.
|
|
||||||
///
|
///
|
||||||
/// Typically sent from one isolate to another.
|
/// **Throws:**
|
||||||
class _SumResponse {
|
/// - [UnsupportedError]: If the platform or output format is not supported.
|
||||||
final int id;
|
/// - [Exception]: If the image decoding or encoding fails.
|
||||||
final int result;
|
///
|
||||||
|
/// **Example - Convert HEIC to JPEG:**
|
||||||
const _SumResponse(this.id, this.result);
|
/// ```dart
|
||||||
|
/// final jpegData = await ImageConverter.convert(
|
||||||
|
/// inputData: heicImageData,
|
||||||
|
/// format: OutputFormat.jpeg,
|
||||||
|
/// quality: 90,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// **Example - Running on the main thread:**
|
||||||
|
/// ```dart
|
||||||
|
/// // Only do this for very small images where isolate overhead is a concern.
|
||||||
|
/// final pngData = await ImageConverter.convert(
|
||||||
|
/// inputData: smallImageData,
|
||||||
|
/// format: OutputFormat.png,
|
||||||
|
/// runInIsolate: false,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
static Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
bool runInIsolate = true,
|
||||||
|
}) {
|
||||||
|
if (runInIsolate) {
|
||||||
|
return compute(
|
||||||
|
_convertInIsolate,
|
||||||
|
_ConvertRequest(inputData, format, quality, defaultTargetPlatform),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// The original implementation for those who opt-out.
|
||||||
|
return _platform.convert(
|
||||||
|
inputData: inputData,
|
||||||
|
format: format,
|
||||||
|
quality: quality,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Counter to identify [_SumRequest]s and [_SumResponse]s.
|
/// Helper class to pass arguments to the isolate.
|
||||||
int _nextSumRequestId = 0;
|
@immutable
|
||||||
|
class _ConvertRequest {
|
||||||
|
final Uint8List inputData;
|
||||||
|
final OutputFormat format;
|
||||||
|
final int quality;
|
||||||
|
final TargetPlatform platform;
|
||||||
|
|
||||||
/// Mapping from [_SumRequest] `id`s to the completers corresponding to the correct future of the pending request.
|
const _ConvertRequest(
|
||||||
final Map<int, Completer<int>> _sumRequests = <int, Completer<int>>{};
|
this.inputData,
|
||||||
|
this.format,
|
||||||
/// The SendPort belonging to the helper isolate.
|
this.quality,
|
||||||
Future<SendPort> _helperIsolateSendPort = () async {
|
this.platform,
|
||||||
// The helper isolate is going to send us back a SendPort, which we want to
|
);
|
||||||
// wait for.
|
|
||||||
final Completer<SendPort> completer = Completer<SendPort>();
|
|
||||||
|
|
||||||
// Receive port on the main isolate to receive messages from the helper.
|
|
||||||
// We receive two types of messages:
|
|
||||||
// 1. A port to send messages on.
|
|
||||||
// 2. Responses to requests we sent.
|
|
||||||
final ReceivePort receivePort = ReceivePort()
|
|
||||||
..listen((dynamic data) {
|
|
||||||
if (data is SendPort) {
|
|
||||||
// The helper isolate sent us the port on which we can sent it requests.
|
|
||||||
completer.complete(data);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (data is _SumResponse) {
|
|
||||||
// The helper isolate sent us a response to a request we sent.
|
/// Returns the platform-specific converter instance.
|
||||||
final Completer<int> completer = _sumRequests[data.id]!;
|
ImageConverterPlatform _getPlatformForTarget(TargetPlatform platform) {
|
||||||
_sumRequests.remove(data.id);
|
if (kIsWeb) {
|
||||||
completer.complete(data.result);
|
throw UnsupportedError('Image conversion is not supported on the web.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
throw UnsupportedError('Unsupported message type: ${data.runtimeType}');
|
return switch (platform) {
|
||||||
});
|
TargetPlatform.android => ImageConverterAndroid(),
|
||||||
|
TargetPlatform.iOS || TargetPlatform.macOS => ImageConverterDarwin(),
|
||||||
// Start the helper isolate.
|
_ => throw UnsupportedError(
|
||||||
await Isolate.spawn((SendPort sendPort) async {
|
'Image conversion is not supported on this platform: $platform',
|
||||||
final ReceivePort helperReceivePort = ReceivePort()
|
),
|
||||||
..listen((dynamic data) {
|
};
|
||||||
// On the helper isolate listen to requests and respond to them.
|
|
||||||
if (data is _SumRequest) {
|
|
||||||
final int result = _bindings.sum_long_running(data.a, data.b);
|
|
||||||
final _SumResponse response = _SumResponse(data.id, result);
|
|
||||||
sendPort.send(response);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
throw UnsupportedError('Unsupported message type: ${data.runtimeType}');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send the port to the main isolate on which we can receive requests.
|
/// Top-level function for `compute`.
|
||||||
sendPort.send(helperReceivePort.sendPort);
|
Future<Uint8List> _convertInIsolate(_ConvertRequest request) {
|
||||||
}, receivePort.sendPort);
|
final platform = _getPlatformForTarget(request.platform);
|
||||||
|
return platform.convert(
|
||||||
// Wait until the helper isolate has sent us back the SendPort on which we
|
inputData: request.inputData,
|
||||||
// can start sending requests.
|
format: request.format,
|
||||||
return completer.future;
|
quality: request.quality,
|
||||||
}();
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
// ignore_for_file: always_specify_types
|
|
||||||
// ignore_for_file: camel_case_types
|
|
||||||
// ignore_for_file: non_constant_identifier_names
|
|
||||||
|
|
||||||
// AUTO GENERATED FILE, DO NOT EDIT.
|
|
||||||
//
|
|
||||||
// Generated by `package:ffigen`.
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
import 'dart:ffi' as ffi;
|
|
||||||
|
|
||||||
/// Bindings for `src/image_ffi.h`.
|
|
||||||
///
|
|
||||||
/// Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
|
|
||||||
///
|
|
||||||
class ImageFfiBindings {
|
|
||||||
/// Holds the symbol lookup function.
|
|
||||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
|
||||||
_lookup;
|
|
||||||
|
|
||||||
/// The symbols are looked up in [dynamicLibrary].
|
|
||||||
ImageFfiBindings(ffi.DynamicLibrary dynamicLibrary)
|
|
||||||
: _lookup = dynamicLibrary.lookup;
|
|
||||||
|
|
||||||
/// The symbols are looked up with [lookup].
|
|
||||||
ImageFfiBindings.fromLookup(
|
|
||||||
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
|
||||||
lookup)
|
|
||||||
: _lookup = lookup;
|
|
||||||
|
|
||||||
/// A very short-lived native function.
|
|
||||||
///
|
|
||||||
/// For very short-lived functions, it is fine to call them on the main isolate.
|
|
||||||
/// They will block the Dart execution while running the native function, so
|
|
||||||
/// only do this for native functions which are guaranteed to be short-lived.
|
|
||||||
int sum(
|
|
||||||
int a,
|
|
||||||
int b,
|
|
||||||
) {
|
|
||||||
return _sum(
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _sumPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>>('sum');
|
|
||||||
late final _sum = _sumPtr.asFunction<int Function(int, int)>();
|
|
||||||
|
|
||||||
/// A longer lived native function, which occupies the thread calling it.
|
|
||||||
///
|
|
||||||
/// Do not call these kind of native functions in the main isolate. They will
|
|
||||||
/// block Dart execution. This will cause dropped frames in Flutter applications.
|
|
||||||
/// Instead, call these native functions on a separate isolate.
|
|
||||||
int sum_long_running(
|
|
||||||
int a,
|
|
||||||
int b,
|
|
||||||
) {
|
|
||||||
return _sum_long_running(
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
late final _sum_long_runningPtr =
|
|
||||||
_lookup<ffi.NativeFunction<ffi.Int Function(ffi.Int, ffi.Int)>>(
|
|
||||||
'sum_long_running');
|
|
||||||
late final _sum_long_running =
|
|
||||||
_sum_long_runningPtr.asFunction<int Function(int, int)>();
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image_ffi/gen/jnigen_bindings.dart';
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
import 'package:jni/jni.dart';
|
||||||
|
|
||||||
|
/// Android image converter using BitmapFactory and Bitmap compression.
|
||||||
|
///
|
||||||
|
/// Implements image conversion for Android 9+ (API level 28+) platforms using
|
||||||
|
/// BitmapFactory for decoding and Bitmap.compress for encoding via JNI.\n///
|
||||||
|
/// **Features:**
|
||||||
|
/// - Supports JPEG, PNG, WebP, GIF, BMP input formats
|
||||||
|
/// - Can read HEIC files (Android 9+)
|
||||||
|
/// - Cannot write HEIC (throws UnsupportedError)
|
||||||
|
/// - Efficient memory usage with ByteArrayOutputStream
|
||||||
|
///
|
||||||
|
/// **API Stack:**
|
||||||
|
/// - `BitmapFactory.decodeByteArray`: Auto-detect and decode input
|
||||||
|
/// - `Bitmap.compress`: Encode to target format with quality control
|
||||||
|
/// - `ByteArrayOutputStream`: Memory-based output buffer
|
||||||
|
///
|
||||||
|
/// **Limitations:**
|
||||||
|
/// - HEIC output not supported (use JPEG or PNG instead)
|
||||||
|
/// - Requires Android 9+ for full format support
|
||||||
|
///
|
||||||
|
/// **Performance:**
|
||||||
|
/// - Native image decoding via BitmapFactory
|
||||||
|
/// - Efficient compression with quality adjustment
|
||||||
|
final class ImageConverterAndroid implements ImageConverterPlatform {
|
||||||
|
@override
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async {
|
||||||
|
final inputJBytes = JByteArray.from(inputData);
|
||||||
|
try {
|
||||||
|
final bitmap = BitmapFactory.decodeByteArray(
|
||||||
|
inputJBytes,
|
||||||
|
0,
|
||||||
|
inputData.length,
|
||||||
|
);
|
||||||
|
if (bitmap == null) {
|
||||||
|
throw Exception('Failed to decode image. Invalid image data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final compressFormat = switch (format) {
|
||||||
|
OutputFormat.jpeg => Bitmap$CompressFormat.JPEG,
|
||||||
|
OutputFormat.png => Bitmap$CompressFormat.PNG,
|
||||||
|
OutputFormat.webp => Bitmap$CompressFormat.WEBP_LOSSY,
|
||||||
|
OutputFormat.heic => throw UnsupportedError(
|
||||||
|
'HEIC output format is not supported on Android.',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final outputStream = ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
final success = bitmap.compress(
|
||||||
|
compressFormat,
|
||||||
|
quality,
|
||||||
|
outputStream,
|
||||||
|
);
|
||||||
|
if (!success) {
|
||||||
|
throw Exception('Failed to compress bitmap.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final outputJBytes = outputStream.toByteArray();
|
||||||
|
if (outputJBytes == null) {
|
||||||
|
throw Exception('Failed to get byte array from output stream.');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final outputList = outputJBytes.toList();
|
||||||
|
return Uint8List.fromList(outputList);
|
||||||
|
} finally {
|
||||||
|
outputJBytes.release();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
outputStream.release();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
compressFormat.release();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
bitmap.release();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
inputJBytes.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:image_ffi/gen/darwin_bindings.dart';
|
||||||
|
import 'package:image_ffi/src/image_converter_platform_interface.dart';
|
||||||
|
import 'package:image_ffi/src/output_format.dart';
|
||||||
|
import 'package:objective_c/objective_c.dart';
|
||||||
|
|
||||||
|
/// iOS/macOS image converter using ImageIO framework.
|
||||||
|
///
|
||||||
|
/// Implements image conversion for iOS 14+ and macOS 10.15+ platforms using
|
||||||
|
/// the native ImageIO framework through FFI bindings.
|
||||||
|
///
|
||||||
|
/// **Features:**
|
||||||
|
/// - Supports all major image formats (JPEG, PNG, HEIC, WebP, etc.)
|
||||||
|
/// - Uses CoreFoundation and CoreGraphics for efficient processing
|
||||||
|
/// - Memory-safe with proper resource cleanup
|
||||||
|
///
|
||||||
|
/// **API Stack:**
|
||||||
|
/// - `CGImageSourceCreateWithData`: Decode input image
|
||||||
|
/// - `CGImageSourceCreateImageAtIndex`: Extract CGImage
|
||||||
|
/// - `CGImageDestinationCreateWithData`: Create output stream
|
||||||
|
/// - `CGImageDestinationAddImage`: Add image with encoding options
|
||||||
|
/// - `CGImageDestinationFinalize`: Complete encoding
|
||||||
|
///
|
||||||
|
/// **Performance:**
|
||||||
|
/// - Direct FFI calls with minimal overhead
|
||||||
|
/// - In-memory processing
|
||||||
|
/// - Adjustable JPEG/WebP quality for size optimization
|
||||||
|
final class ImageConverterDarwin implements ImageConverterPlatform {
|
||||||
|
@override
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) async {
|
||||||
|
final inputPtr = calloc<Uint8>(inputData.length);
|
||||||
|
try {
|
||||||
|
inputPtr.asTypedList(inputData.length).setAll(0, inputData);
|
||||||
|
|
||||||
|
final cfData = CFDataCreate(
|
||||||
|
kCFAllocatorDefault,
|
||||||
|
inputPtr.cast(),
|
||||||
|
inputData.length,
|
||||||
|
);
|
||||||
|
if (cfData == nullptr) {
|
||||||
|
throw Exception('Failed to create CFData from input data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final imageSource = CGImageSourceCreateWithData(cfData, nullptr);
|
||||||
|
if (imageSource == nullptr) {
|
||||||
|
throw Exception('Failed to create CGImageSource. Invalid image data.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final cgImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
|
||||||
|
if (cgImage == nullptr) {
|
||||||
|
throw Exception('Failed to decode image.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final outputData = CFDataCreateMutable(kCFAllocatorDefault, 0);
|
||||||
|
if (outputData == nullptr) {
|
||||||
|
throw Exception('Failed to create output CFData.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final utiStr = switch (format) {
|
||||||
|
// https://developer.apple.com/documentation/uniformtypeidentifiers/uttypejpeg
|
||||||
|
OutputFormat.jpeg => 'public.jpeg',
|
||||||
|
// https://developer.apple.com/documentation/uniformtypeidentifiers/uttypepng
|
||||||
|
OutputFormat.png => 'public.png',
|
||||||
|
// https://developer.apple.com/documentation/uniformtypeidentifiers/uttypeheic
|
||||||
|
OutputFormat.heic => 'public.heic',
|
||||||
|
// https://developer.apple.com/documentation/uniformtypeidentifiers/uttypewebp
|
||||||
|
OutputFormat.webp => throw UnsupportedError(
|
||||||
|
'WebP output format is not supported on iOS/macOS via ImageIO.',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
final cfString = utiStr
|
||||||
|
.toNSString()
|
||||||
|
.ref
|
||||||
|
.retainAndAutorelease()
|
||||||
|
.cast<CFString>();
|
||||||
|
|
||||||
|
final destination = CGImageDestinationCreateWithData(
|
||||||
|
outputData,
|
||||||
|
cfString,
|
||||||
|
1,
|
||||||
|
nullptr,
|
||||||
|
);
|
||||||
|
if (destination == nullptr) {
|
||||||
|
throw Exception('Failed to create CGImageDestination.');
|
||||||
|
}
|
||||||
|
|
||||||
|
CGImageDestinationAddImage(destination, cgImage, nullptr);
|
||||||
|
|
||||||
|
final success = CGImageDestinationFinalize(destination);
|
||||||
|
if (!success) {
|
||||||
|
throw Exception('Failed to finalize image encoding.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final length = CFDataGetLength(outputData);
|
||||||
|
final bytePtr = CFDataGetBytePtr(outputData);
|
||||||
|
if (bytePtr == nullptr) {
|
||||||
|
throw Exception('Failed to get output data bytes.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uint8List.fromList(bytePtr.cast<Uint8>().asTypedList(length));
|
||||||
|
} finally {
|
||||||
|
calloc.free(inputPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'output_format.dart';
|
||||||
|
|
||||||
|
/// Platform-specific image converter interface.
|
||||||
|
///
|
||||||
|
/// Abstract base class that all platform implementations must implement.
|
||||||
|
/// Handles the core logic of image format conversion on each platform.
|
||||||
|
///
|
||||||
|
/// **Implementations:**
|
||||||
|
/// - [ImageConverterDarwin]: iOS and macOS using ImageIO
|
||||||
|
/// - [ImageConverterAndroid]: Android using BitmapFactory
|
||||||
|
abstract interface class ImageConverterPlatform {
|
||||||
|
/// Converts an image to a target format.
|
||||||
|
///
|
||||||
|
/// Decodes the input image data and re-encodes it in the specified format.
|
||||||
|
///
|
||||||
|
/// **Parameters:**
|
||||||
|
/// - [inputData]: Raw bytes of the image to convert
|
||||||
|
/// - [format]: Target [OutputFormat] (default: [OutputFormat.jpeg])
|
||||||
|
/// - [quality]: Compression quality 1-100 for lossy formats (default: 95)
|
||||||
|
///
|
||||||
|
/// **Returns:** Converted image data as [Uint8List]
|
||||||
|
///
|
||||||
|
/// **Throws:**
|
||||||
|
/// - [UnimplementedError]: If not implemented by platform subclass
|
||||||
|
/// - [UnsupportedError]: If format is not supported
|
||||||
|
/// - [Exception]: If conversion fails
|
||||||
|
Future<Uint8List> convert({
|
||||||
|
required Uint8List inputData,
|
||||||
|
OutputFormat format = OutputFormat.jpeg,
|
||||||
|
int quality = 100,
|
||||||
|
}) {
|
||||||
|
throw UnimplementedError('convert() has not been implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/// Output image format for conversion.
|
||||||
|
///
|
||||||
|
/// Specifies the target format when converting images.
|
||||||
|
///
|
||||||
|
/// **Format Support:**
|
||||||
|
/// | Format | iOS/macOS | Android |
|
||||||
|
/// |--------|-----------|---------|
|
||||||
|
/// | jpeg | ✓ | ✓ |
|
||||||
|
/// | png | ✓ | ✓ |
|
||||||
|
/// | webp | ✓ | ✓ |
|
||||||
|
///
|
||||||
|
/// **Notes:**
|
||||||
|
/// - [jpeg]: Good compression with adjustable quality
|
||||||
|
/// - [png]: Lossless compression, supports transparency
|
||||||
|
/// - [webp]: Modern format with better compression than JPEG
|
||||||
|
enum OutputFormat {
|
||||||
|
/// JPEG format (.jpg, .jpeg)
|
||||||
|
/// Lossy compression, suitable for photos
|
||||||
|
jpeg,
|
||||||
|
|
||||||
|
/// PNG format (.png)
|
||||||
|
/// Lossless compression, supports transparency
|
||||||
|
png,
|
||||||
|
|
||||||
|
/// WebP format (.webp)
|
||||||
|
/// Modern format with superior compression (not supported on Darwin)
|
||||||
|
webp,
|
||||||
|
|
||||||
|
/// HEIC format (.heic)
|
||||||
|
/// High Efficiency Image Format (not supported on Android)
|
||||||
|
heic,
|
||||||
|
}
|
||||||
20
pubspec.yaml
20
pubspec.yaml
|
|
@ -1,7 +1,7 @@
|
||||||
name: image_ffi
|
name: image_ffi
|
||||||
description: "A new Flutter FFI plugin project."
|
description: "A high-performance Flutter plugin for cross-platform image format conversion using native APIs."
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
homepage:
|
homepage: https://github.com/koji-1009/image_ffi
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.10.0
|
sdk: ^3.10.0
|
||||||
|
|
@ -10,19 +10,13 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
plugin_platform_interface: ^2.0.2
|
jni: ^0.15.2
|
||||||
|
ffi: ^2.1.4
|
||||||
|
objective_c: ^9.2.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
ffi: ^2.1.3
|
|
||||||
ffigen: ^20.0.0
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
|
jnigen: ^0.15.0
|
||||||
flutter:
|
ffigen: ^20.1.1
|
||||||
plugin:
|
|
||||||
platforms:
|
|
||||||
android:
|
|
||||||
ffiPlugin: true
|
|
||||||
ios:
|
|
||||||
ffiPlugin: true
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
# The Flutter tooling requires that developers have CMake 3.10 or later
|
|
||||||
# installed. You should not increase this version, as doing so will cause
|
|
||||||
# the plugin to fail to compile for some customers of the plugin.
|
|
||||||
cmake_minimum_required(VERSION 3.10)
|
|
||||||
|
|
||||||
project(image_ffi_library VERSION 0.0.1 LANGUAGES C)
|
|
||||||
|
|
||||||
add_library(image_ffi SHARED
|
|
||||||
"image_ffi.c"
|
|
||||||
)
|
|
||||||
|
|
||||||
set_target_properties(image_ffi PROPERTIES
|
|
||||||
PUBLIC_HEADER image_ffi.h
|
|
||||||
OUTPUT_NAME "image_ffi"
|
|
||||||
)
|
|
||||||
|
|
||||||
target_compile_definitions(image_ffi PUBLIC DART_SHARED_LIB)
|
|
||||||
|
|
||||||
if (ANDROID)
|
|
||||||
# Support Android 15 16k page size
|
|
||||||
target_link_options(image_ffi PRIVATE "-Wl,-z,max-page-size=16384")
|
|
||||||
endif()
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#include "image_ffi.h"
|
|
||||||
|
|
||||||
// A very short-lived native function.
|
|
||||||
//
|
|
||||||
// For very short-lived functions, it is fine to call them on the main isolate.
|
|
||||||
// They will block the Dart execution while running the native function, so
|
|
||||||
// only do this for native functions which are guaranteed to be short-lived.
|
|
||||||
FFI_PLUGIN_EXPORT int sum(int a, int b) { return a + b; }
|
|
||||||
|
|
||||||
// A longer-lived native function, which occupies the thread calling it.
|
|
||||||
//
|
|
||||||
// Do not call these kind of native functions in the main isolate. They will
|
|
||||||
// block Dart execution. This will cause dropped frames in Flutter applications.
|
|
||||||
// Instead, call these native functions on a separate isolate.
|
|
||||||
FFI_PLUGIN_EXPORT int sum_long_running(int a, int b) {
|
|
||||||
// Simulate work.
|
|
||||||
#if _WIN32
|
|
||||||
Sleep(5000);
|
|
||||||
#else
|
|
||||||
usleep(5000 * 1000);
|
|
||||||
#endif
|
|
||||||
return a + b;
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#if _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#else
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if _WIN32
|
|
||||||
#define FFI_PLUGIN_EXPORT __declspec(dllexport)
|
|
||||||
#else
|
|
||||||
#define FFI_PLUGIN_EXPORT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// A very short-lived native function.
|
|
||||||
//
|
|
||||||
// For very short-lived functions, it is fine to call them on the main isolate.
|
|
||||||
// They will block the Dart execution while running the native function, so
|
|
||||||
// only do this for native functions which are guaranteed to be short-lived.
|
|
||||||
FFI_PLUGIN_EXPORT int sum(int a, int b);
|
|
||||||
|
|
||||||
// A longer lived native function, which occupies the thread calling it.
|
|
||||||
//
|
|
||||||
// Do not call these kind of native functions in the main isolate. They will
|
|
||||||
// block Dart execution. This will cause dropped frames in Flutter applications.
|
|
||||||
// Instead, call these native functions on a separate isolate.
|
|
||||||
FFI_PLUGIN_EXPORT int sum_long_running(int a, int b);
|
|
||||||
Loading…
Reference in New Issue