native_cutout 0.2.0
native_cutout: ^0.2.0 copied to clipboard
On-device background removal for Flutter. iOS Vision (iOS 17+) and Android ML Kit (model downloaded on demand). Outputs transparent PNG.
native_cutout #
English | 简体中文
Native background removal for Flutter using platform image segmentation APIs.
native_cutout removes the background from a local image file and produces a transparent PNG — fully on-device, with no external API, no upload step, and no API keys.
| Home · model manager | Cutout result |
|---|---|
![]() |
![]() |
It is built on top of:
- iOS: Vision Framework (
VNGenerateForegroundInstanceMaskRequest) - Android: Google ML Kit Subject Segmentation
Features #
- Fully on-device background removal
- Default file-backed output written to the app cache directory
- Optional in-memory PNG bytes output when you need bytes in Dart
- Native image processing on both iOS and Android
- Handles EXIF orientation before segmentation
- Optional subject-bound cropping via
CutoutOptions.cropToSubject - Cache management via
clearCache() - Android model lifecycle helpers: availability check, download, progress, and clear
- Simple Dart API with typed success/failure results
Platform support #
| Platform | Engine | Minimum version | Notes |
|---|---|---|---|
| iOS | Vision Framework | iOS 13.0 (compile) / iOS 17.0 (runtime) | Requires a real device for actual processing |
| Android | ML Kit Subject Segmentation | API 21+ | Segmentation model is downloaded by Google Play services on demand |
Important
- On iOS Simulator, foreground extraction is not available. Use a real iPhone or iPad.
- On Android, the ML Kit model is not bundled in your APK. If it isn't already on the device, the first call to
removeBackgroundwill trigger an implicit download via Google Play services — that first call will block until the download completes and will fail when the device is offline. See Android setup for the recommended explicit warm-up flow.
Installation #
Add the package to your pubspec.yaml:
dependencies:
native_cutout: ^0.1.0
Then run:
flutter pub get
iOS setup #
The plugin compiles against iOS 13.0+ (Flutter's default), so it will not
raise your app's deployment target. At runtime the background-removal API
(VNGenerateForegroundInstanceMaskRequest) requires iOS 17.0+ — on older
versions removeBackground returns the error code UNSUPPORTED_OS.
No Podfile changes are required. Just run pods as usual:
cd ios && pod install
Android setup #
The plugin supports Android API 21+ and requires no manual AndroidManifest.xml changes.
How the ML Kit model is delivered #
This plugin uses the unbundled variant of ML Kit Subject Segmentation
(play-services-mlkit-subject-segmentation). The segmentation model is not
shipped inside your APK — it is delivered by Google Play services and lives
outside your app. There are two ways the model gets onto the device:
-
Implicit download (default, lazy) — the first call to
NativeCutout.removeBackgroundon a device that does not yet have the model triggers an internal download inside ML Kit. TheremoveBackgroundfuture will not complete until the download finishes, which means:- The very first cutout on a fresh install will take noticeably longer
- It will fail when the device is offline (typical error:
processingFailed) - There is no progress callback during this implicit download — your UI can only show an indeterminate spinner
-
Explicit warm-up (recommended) — call
NativeCutout.downloadModel()ahead of time (e.g. on app launch, or the first time the user opens the editor). This path streams progress throughNativeCutout.downloadProgress, lets you render a real download UI, and removes the first-cutout latency spike.
See the Recommended Android warm-up flow below for code.
For testing, NativeCutout.clearModel() requests Google Play services to
release the downloaded module so you can re-exercise the first-download path.
Quick start #
Default flow: return a cached PNG file path #
By default, native_cutout writes the result PNG to the app cache directory and returns a file path.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:native_cutout/native_cutout.dart';
final result = await NativeCutout.removeBackground(
imagePath,
options: const CutoutOptions(
cropToSubject: true,
writeToCache: true,
),
);
late final Widget preview;
switch (result) {
case CutoutFileSuccess(:final path):
preview = Image.file(File(path));
break;
case CutoutBytesSuccess(:final pngBytes):
preview = Image.memory(pngBytes);
break;
case CutoutFailure(:final code, :final message):
debugPrint('Cutout failed: ${code.name} - $message');
return;
}
In-memory flow: return PNG bytes directly #
If you explicitly need raw bytes in Dart, disable cache writing:
final result = await NativeCutout.removeBackground(
imagePath,
options: const CutoutOptions(writeToCache: false),
);
Recommended Android warm-up flow #
If you want a more reliable first-run experience on Android, check the model before processing:
final isReady = await NativeCutout.isModelAvailable();
if (!isReady) {
final downloaded = await NativeCutout.downloadModel();
if (!downloaded) {
debugPrint('ML Kit model download failed');
return;
}
}
final result = await NativeCutout.removeBackground(imagePath);
If you want to surface download progress in your UI:
final sub = NativeCutout.downloadProgress.listen((progress) {
debugPrint('state=${progress.state} fraction=${progress.fraction}');
});
final ok = await NativeCutout.downloadModel();
await sub.cancel();
If you need to re-test the first-download flow on Android:
await NativeCutout.clearModel();
final isStillAvailable = await NativeCutout.isModelAvailable();
debugPrint('available after clear: $isStillAvailable');
If you are using cached file output, you can also clear old generated PNGs:
await NativeCutout.clearCache();
On iOS:
isModelAvailable()always returnstruedownloadModel()is a no-op and returnstrueclearModel()is a no-op and returnstrue
API overview #
NativeCutout.removeBackground #
Removes the background from a local image file.
Future<CutoutResult> NativeCutout.removeBackground(
String imagePath, {
CutoutOptions? options,
})
Parameters:
imagePath: absolute path to a local image file on device storageoptions: optional cutout configuration
Returns:
CutoutFileSuccesswith a cached PNG file path (default)CutoutBytesSuccesswith PNG bytes whenwriteToCacheisfalseCutoutFailurewithcodeandmessage
CutoutOptions #
const CutoutOptions(
cropToSubject: false,
writeToCache: true,
)
Available fields:
cropToSubject: whentrue, trims transparent margins and returns a tight subject crop; whenfalse, preserves the original image canvas sizewriteToCache: whentrue(default), writes the PNG to the app cache directory and returnsCutoutFileSuccess; whenfalse, returnsCutoutBytesSuccess
NativeCutout.clearCache #
Deletes PNG files previously written by the plugin into the app cache directory.
Future<bool> NativeCutout.clearCache()
NativeCutout.isModelAvailable #
Checks whether the native model/runtime is ready to use.
Future<bool> NativeCutout.isModelAvailable()
NativeCutout.downloadModel #
Triggers download of the Android ML Kit segmentation module when needed.
Future<bool> NativeCutout.downloadModel()
NativeCutout.clearModel #
Requests release of the downloaded Android ML Kit module.
Future<bool> NativeCutout.clearModel()
Note
On Android this delegates to Google Play services
releaseModules(...), which is a best-effort request. The model may not disappear immediately, so callisModelAvailable()again to refresh the current state.
NativeCutout.downloadProgress #
Broadcast stream of Android model download progress events.
Stream<ModelDownloadProgress> get NativeCutout.downloadProgress
Notes:
- Only emits on Android while
downloadModel()is running - Returns an empty stream on iOS
- Each event includes
state,bytesDownloaded,totalBytes,errorCode, and computedfraction
Result types #
CutoutFileSuccess #
Successful result containing a cached PNG path:
class CutoutFileSuccess extends CutoutSuccess {
final String path;
}
CutoutBytesSuccess #
Successful result containing in-memory PNG bytes:
class CutoutBytesSuccess extends CutoutSuccess {
final Uint8List pngBytes;
}
CutoutFailure #
Failed result containing a typed error code and readable message:
class CutoutFailure extends CutoutResult {
final CutoutErrorCode code;
final String message;
}
Error codes #
| Error code | Meaning |
|---|---|
invalidInput |
The image path is missing, invalid, or the file could not be decoded |
noSubjectFound |
No clear foreground subject was detected in the image |
processingFailed |
Native processing failed for another reason |
Output behavior #
- The output is always a PNG with transparent background
- By default, the plugin writes the PNG to the app cache directory and returns a file path
- If
writeToCacheisfalse, the plugin returns PNG bytes in memory - The background is made transparent
- Transparent borders are trimmed only when
cropToSubjectis set totrue - Cached PNGs can be removed with
NativeCutout.clearCache()
Best results #
For the highest-quality cutout:
- Use images with a clear foreground subject
- Prefer images where the subject is visually separated from the background
- Avoid heavily blurred, extremely dark, or very low-resolution inputs
Limitations #
- Input must be a local file path
- iOS processing requires iOS 17+ and a real device
- Android requires Google Play services and an initial model download (implicit on first use, or explicit via
downloadModel()); the first call is offline-unfriendly if the model isn't already cached - The quality of the result depends on the platform segmentation engine and source image quality
Example app #
The example/ app in this repository demonstrates:
- picking an image from the gallery
- checking/downloading the Android model
- observing Android model download progress
- clearing the Android model and refreshing availability for repeat testing
- running background removal
- toggling
cropToSubjecton the result page - toggling
writeToCacheto compare cache-file vs memory-bytes output - previewing before/after results
- comparing cached-file output dimensions with the original image
- saving the transparent PNG output
License #
MIT

