seika
Smart on-device image inpainting for Flutter.
Seika combines a zero-MB PatchMatch engine (works offline, no download needed) with downloadable neural models โ LaMa and MI-GAN โ running via ONNX Runtime. Pick the engine manually or let seika route automatically based on mask complexity.
Features
- ๐ผ Three engines โ PatchMatch (0 MB), LaMa (209 MB), MI-GAN (80 MB)
- ๐ฆ On-demand model downloads โ models are stored on device, not bundled in the APK or IPA. Your app stays small.
- ๐ Auto routing โ analyzes texture variance and edge coherence to pick the best engine automatically
- ๐ญ Mask dilation โ expands the mask 8 px before neural inference to reduce edge artifacts
- ๐ชก Soft boundary blending โ BFS distance-field blend for seamless transitions between filled and original regions
- ๐ SHA-256 verification โ model integrity checked after download
- ๐งต Isolate-safe โ PatchMatch runs in a background isolate, neural
inference runs via
OrtSession.runAsync. Both are safe to call from the UI thread. - ๐ฑ Android & iOS โ native C compiled as
.soon Android and asseika.frameworkon iOS via Flutter native assets
Platform support
| Platform | PatchMatch | LaMa | MI-GAN | Min version |
|---|---|---|---|---|
| Android | โ | โ | โ | API 21 |
| iOS | โ | โ | โ | iOS 16.0 |
Installation
dependencies:
seika: ^0.1.0
Android
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
iOS
Set the minimum deployment target to iOS 16.0 in ios/Podfile:
platform :ios, '16.0'
onnxruntime-objc 1.23.0 (used internally) requires iOS 16.0 or higher.
Quick start
import 'package:seika/seika.dart';
final seika = Seika();
if (!await seika.modelManager.isAvailable()) {
await for (final p in seika.modelManager.download()) {
print('${(p.fraction * 100).toStringAsFixed(0)}%');
}
}
// 2. Inpaint โ auto-selects the best engine
final result = await seika.inpaint(
rgbaBytes, // Uint8List โ RGBA pixels, width ร height ร 4 bytes
maskBytes, // Uint8List โ grayscale mask, width ร height bytes
// 255 = fill this pixel, 0 = keep original
width,
height,
);
print(result.engineUsed); // SeikaEngine.neural
print(result.duration); // Duration(milliseconds: 1200)
// result.image โ Uint8List RGBA ready for display
Engines
| Engine | Download | Quality | Speed | Best for |
|---|---|---|---|---|
| PatchMatch | 0 MB | โ โ โ โ | Fast | Textures, sky, walls, simple backgrounds |
| LaMa | 209 MB | โ โ โ โ | Medium | Object removal, watermarks, text |
| MI-GAN | 80 MB | โ โ โ โ | Fast | Mobile deployments, small objects |
PatchMatch
Pure C implementation โ no model files required, works fully offline. Uses a multi-scale pyramid, bidirectional NNF with EM voting, and seam blending.
LaMa
Large Mask inpainting via Fast Fourier Convolutions (WACV 2022). ONNX model from Carve/LaMa-ONNX.
MI-GAN
Mobile Inpainting GAN (ICCV 2023) by Picsart AI Research. Pipeline ONNX with built-in pre/post-processing. 7ร smaller than LaMa.
API reference
Seika
The main entry point.
// Default model: LaMa
final seika = Seika();
// Start with a specific model
final seika = Seika(model: SeikaModelInfo.migan);
inpaint
Future<SeikaInpaintResult> inpaint(
Uint8List rgba,
Uint8List mask,
int width,
int height, {
SeikaInpaintOptions options = const SeikaInpaintOptions(),
})
| Parameter | Type | Description |
|---|---|---|
rgba |
Uint8List |
RGBA pixels, width ร height ร 4 bytes |
mask |
Uint8List |
Grayscale mask, width ร height bytes. 255 = fill, 0 = keep |
options.engine |
SeikaEngine |
auto (default), patchMatch, or neural |
options.quality |
SeikaQuality |
fast, balanced (default), or quality |
Returns SeikaInpaintResult:
| Field | Type | Description |
|---|---|---|
image |
Uint8List |
Output RGBA pixels |
engineUsed |
SeikaEngine |
Which engine ran |
duration |
Duration |
Wall-clock time |
analysis |
SeikaAnalysis |
Mask complexity metrics |
analyze
Runs the complexity analyzer without inpainting:
final analysis = await seika.analyze(rgba, mask, width, height);
print(analysis.recommended); // SeikaEngine.neural
print(analysis.maskRatio); // 0.12
print(analysis.textureVariance); // 340.5
print(analysis.edgeCoherence); // 0.87
switchModel
await seika.switchModel(SeikaModelInfo.migan);
Disposes the current ONNX session and prepares a different model for the
next inpaint() call. The previous model file on disk is not deleted.
unloadModel
await seika.unloadModel();
Releases the ONNX session from memory without changing the selected model.
Useful when the app goes to background. The next inpaint() call reloads
the model from disk.
SeikaModelManager
Manages the lifecycle of a downloadable ONNX model.
final manager = SeikaModelManager(info: SeikaModelInfo.lama);
// Check if the model file is already on disk
final ready = await manager.isAvailable();
// Download with progress
await for (final p in manager.download()) {
print('${(p.fraction * 100).toInt()}% โ ${p.bytesReceived} bytes');
}
// Get the local file path (to load manually)
final path = await manager.localPath();
// Delete from disk to free storage
await manager.delete();
SeikaModelInfo
Static model descriptors. Extend the catalog by adding your own entries.
SeikaModelInfo.lama // LaMa FP32, 209 MB
SeikaModelInfo.migan // MI-GAN pipeline v2, 80 MB
SeikaModelInfo.all // List<SeikaModelInfo> of all built-in models
Each entry exposes:
model.id // 'lama-fp32'
model.name // 'LaMa'
model.description // 'High quality inpainting...'
model.sizeMb // '209 MB'
model.tags // ['high quality', 'uniform backgrounds', 'watermarks']
model.type // SeikaModelType.lama
SeikaInpaintOptions
const SeikaInpaintOptions({
SeikaEngine engine = SeikaEngine.auto,
SeikaQuality quality = SeikaQuality.balanced,
})
| Quality | PatchMatch config | Neural |
|---|---|---|
fast |
patch=5, 2 iterations, single-scale | same model |
balanced |
patch=7, 5 iterations, 3-level pyramid | same model |
quality |
patch=9, 8 iterations, 4-level pyramid | same model |
Quality only affects PatchMatch. LaMa and MI-GAN always run the same ONNX model regardless of quality setting.
Model catalog โ adding a custom model
static const myModel = SeikaModelInfo(
id: 'my-model-v1',
name: 'My Model',
description: 'Custom inpainting model.',
url: 'https://example.com/model.onnx',
sha256: 'abc123...', // leave empty to skip verification
sizeBytes: 50_000_000,
version: 'my-model-v1',
type: SeikaModelType.lama, // or SeikaModelType.migan
tags: ['custom'],
);
LaMa-compatible models must accept:
image:Float32[1, 3, 512, 512]normalized to[0, 1]mask:Float32[1, 1, 512, 512]binary (1.0= fill)- Output:
Float32[1, 3, 512, 512]in[0, 255]range
MI-GAN-compatible models must accept:
image:Uint8[1, 3, H, W]RGB (CHW)mask:Uint8[1, 1, H, W]โ0= fill,255= keep- Output:
Uint8[1, 3, H, W]RGB
Neural path improvements (applied automatically)
Seika applies two improvements on every neural inference call:
1. Mask dilation โ expands the user mask by 8 px before passing it to the model. The model receives more boundary context and produces better fills at the edges of the masked region.
2. Soft boundary blending โ after inference, a BFS distance field computes the distance from the mask boundary for each pixel. Pixels near the boundary blend smoothly between the neural output and the original image over a 10 px transition zone, eliminating visible seams.
Both improvements use the original (non-dilated) mask for the final pixel restoration, so the fill is precise to what the user painted.
License
MIT โ see LICENSE.
Libraries
- seika
- Smart inpainting for Flutter.
- seika_bindings_generated