Files
odysseus/swift/odysseus-mlx-image-bridge/Sources/OdysseusMLXInpaint/main.swift
T
2026-07-23 14:49:02 +00:00

89 lines
2.8 KiB
Swift

import Foundation
import CoreGraphics
import ImageIO
import UniformTypeIdentifiers
import MLX
import LaMa
import MIGAN
struct Args {
var model = ""
var image = ""
var mask = ""
var output = ""
var mode = ""
}
func value(after flag: String, in args: [String]) -> String? {
guard let i = args.firstIndex(of: flag), i + 1 < args.count else { return nil }
return args[i + 1]
}
func parseArgs() throws -> Args {
let argv = Array(CommandLine.arguments.dropFirst())
var out = Args()
out.model = value(after: "--model", in: argv) ?? ""
out.image = value(after: "--image", in: argv) ?? ""
out.mask = value(after: "--mask", in: argv) ?? ""
out.output = value(after: "--output", in: argv) ?? ""
out.mode = value(after: "--mode", in: argv) ?? ""
guard !out.model.isEmpty, !out.image.isEmpty, !out.mask.isEmpty, !out.output.isEmpty else {
throw BridgeError.usage("usage: odysseus-mlx-inpaint --model weights.safetensors --image input.png --mask mask.png --output output.png [--mode best|fast]")
}
return out
}
func decodeCGImage(_ path: String) throws -> CGImage {
let url = URL(fileURLWithPath: path)
guard let src = CGImageSourceCreateWithURL(url as CFURL, nil),
let cg = CGImageSourceCreateImageAtIndex(src, 0, nil) else {
throw BridgeError.decode(path)
}
return cg
}
func encodePNG(_ image: CGImage, _ path: String) throws {
let url = URL(fileURLWithPath: path)
guard let dest = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil) else {
throw BridgeError.encode(path)
}
CGImageDestinationAddImage(dest, image, nil)
guard CGImageDestinationFinalize(dest) else { throw BridgeError.encode(path) }
}
enum BridgeError: Error, CustomStringConvertible {
case usage(String)
case decode(String)
case encode(String)
var description: String {
switch self {
case .usage(let s): return s
case .decode(let p): return "failed to decode image: \(p)"
case .encode(let p): return "failed to write PNG: \(p)"
}
}
}
do {
let args = try parseArgs()
let source = try decodeCGImage(args.image)
let mask = try decodeCGImage(args.mask)
let lower = args.model.lowercased()
let mode = args.mode.lowercased()
let output: CGImage
if lower.contains("mi-gan") || lower.contains("migan") || mode == "fast" {
let resolution = lower.contains("512") ? 512 : 256
let inpainter = try MIGANInpainter.fromPretrained(args.model, resolution: resolution, dtype: .float16)
output = inpainter(source, mask: mask)
} else {
let inpainter = try LaMaInpainter.fromPretrained(args.model, dtype: .bfloat16)
output = inpainter(source, mask: mask)
}
try encodePNG(output, args.output)
} catch {
fputs("\(error)\n", stderr)
exit(1)
}