Skip to content

Instantly share code, notes, and snippets.

@zats
Created September 10, 2026 18:56
Show Gist options
  • Select an option

  • Save zats/6332c0c9b75e861ea1f384a70ba9531c to your computer and use it in GitHub Desktop.

Select an option

Save zats/6332c0c9b75e861ea1f384a70ba9531c to your computer and use it in GitHub Desktop.
Using private CIInpaintingFilter on macOS to recreate Photos "Clean up" feature
import Foundation
import CoreImage
struct CleanupError: LocalizedError {
let message: String
var errorDescription: String? { message }
}
/// Remove a rectangle using the OS inpainting model. Rect uses whole pixels from the top-left of the oriented image.
public func cleanUp(_ image: CIImage, rect: CGRect) throws -> CIImage {
let source = image.transformed(by: .init(translationX: -image.extent.minX, y: -image.extent.minY))
guard rect.width > 0, rect.height > 0, rect == rect.integral, source.extent.contains(rect) else {
throw CleanupError(message: "The rectangle must use whole pixels and fit inside the image.")
}
guard let filter = CIFilter(name: "CIInpaintingFilter") else {
throw CleanupError(message: "This OS does not provide the inpainting filter.")
}
let area = CGRect(x: rect.minX, y: source.extent.height - rect.maxY, width: rect.width, height: rect.height)
let mask = CIImage(color: .white).cropped(to: area).composited(over: CIImage(color: .black).cropped(to: source.extent))
filter.setValue(source, forKey: kCIInputImageKey)
// The inpainting filter removes black pixels; the blend mask uses white.
filter.setValue(mask.applyingFilter("CIColorInvert"), forKey: kCIInputMaskImageKey)
filter.setValue(CIVector(cgRect: area), forKey: "inputMaskBoundingBox")
guard let generated = filter.outputImage else {
throw CleanupError(message: "The inpainting filter did not return an image.")
}
return generated.applyingFilter("CIBlendWithMask", parameters: [kCIInputBackgroundImageKey: source, kCIInputMaskImageKey: mask]).cropped(to: source.extent)
}
import Foundation
import CoreImage
do {
let args = CommandLine.arguments
guard args.count == 6 || args.count == 7 else {
throw CleanupError(message: "Usage: cleanup-image INPUT X Y WIDTH HEIGHT [OUTPUT.png]")
}
let values = args[2...5].compactMap(Int.init)
guard values.count == 4 else {
throw CleanupError(message: "X, Y, WIDTH and HEIGHT must be integers in pixels.")
}
let inputURL = URL(fileURLWithPath: args[1])
let outputURL = args.count == 7 ? URL(fileURLWithPath: args[6]) : inputURL.deletingPathExtension().appendingPathExtension("cleaned.png")
guard !FileManager.default.fileExists(atPath: outputURL.path) else {
throw CleanupError(message: "The output already exists. Choose a new output path.")
}
guard let source = CIImage(contentsOf: inputURL, options: [.applyOrientationProperty: true]) else {
throw CleanupError(message: "Core Image cannot read the input image.")
}
let rect = CGRect(x: values[0], y: values[1], width: values[2], height: values[3])
let result = try cleanUp(source, rect: rect)
try CIContext().writePNGRepresentation(of: result, to: outputURL, format: .RGBA8, colorSpace: CGColorSpace(name: CGColorSpace.sRGB)!)
print(outputURL.path)
} catch {
FileHandle.standardError.write(Data("\(error.localizedDescription)\n".utf8))
exit(1)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment