mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-01 19:18:35 -04:00
89 lines
2.8 KiB
Swift
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)
|
|
}
|