seika

pub version pub points license platform

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 .so on Android and as seika.framework on 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