Skip to content

Instantly share code, notes, and snippets.

@nicolas-miari
Created November 13, 2021 00:31
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicolas-miari/519cb8fd31c16e5daac263412996d08a to your computer and use it in GitHub Desktop.
Save nicolas-miari/519cb8fd31c16e5daac263412996d08a to your computer and use it in GitHub Desktop.
Swift Image Compare
import Foundation
import CoreImage
/**
Based on: https://gist.github.com/SheffieldKevin/566dc048dd6f36716bcd
Updated for Swift 5.5 (Xcode 13)
*/
class ImageDiff {
func compare(leftImage: CGImage, rightImage: CGImage) throws -> Int {
let left = CIImage(cgImage: leftImage)
let right = CIImage(cgImage: rightImage)
guard let diffFilter = CIFilter(name: "CIDifferenceBlendMode") else {
throw ImageDiffError.failedToCreateFilter
}
diffFilter.setDefaults()
diffFilter.setValue(left, forKey: kCIInputImageKey)
diffFilter.setValue(right, forKey: kCIInputBackgroundImageKey)
// Create the area max filter and set its properties.
guard let areaMaxFilter = CIFilter(name: "CIAreaMaximum") else {
throw ImageDiffError.failedToCreateFilter
}
areaMaxFilter.setDefaults()
areaMaxFilter.setValue(diffFilter.value(forKey: kCIOutputImageKey),
forKey: kCIInputImageKey)
let compareRect = CGRect(x: 0, y: 0, width: CGFloat(leftImage.width), height: CGFloat(leftImage.height))
let extents = CIVector(cgRect: compareRect)
areaMaxFilter.setValue(extents, forKey: kCIInputExtentKey)
// The filters have been setup, now set up the CGContext bitmap context the
// output is drawn to. Setup the context with our supplied buffer.
let alphaInfo = CGImageAlphaInfo.premultipliedLast
let bitmapInfo = CGBitmapInfo(rawValue: alphaInfo.rawValue)
let colorSpace = CGColorSpaceCreateDeviceRGB()
var buf: [CUnsignedChar] = Array<CUnsignedChar>(repeating: 255, count: 16)
guard let context = CGContext(
data: &buf,
width: 1,
height: 1,
bitsPerComponent: 8,
bytesPerRow: 16,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue
) else {
throw ImageDiffError.failedToCreateContext
}
// Now create the core image context CIContext from the bitmap context.
let ciContextOpts = [
CIContextOption.workingColorSpace : colorSpace,
CIContextOption.useSoftwareRenderer : false
] as [CIContextOption : Any]
let ciContext = CIContext(cgContext: context, options: ciContextOpts)
// Get the output CIImage and draw that to the Core Image context.
let valueImage = areaMaxFilter.value(forKey: kCIOutputImageKey)! as! CIImage
ciContext.draw(valueImage, in: CGRect(x: 0, y: 0, width: 1, height: 1),
from: valueImage.extent)
// This will have modified the contents of the buffer used for the CGContext.
// Find the maximum value of the different color components. Remember that
// the CGContext was created with a Premultiplied last meaning that alpha
// is the fourth component with red, green and blue in the first three.
let maxVal = max(buf[0], max(buf[1], buf[2]))
let diff = Int(maxVal)
return diff
}
}
// MARK: - Supporting Types
enum ImageDiffError: LocalizedError {
case failedToCreateFilter
case failedToCreateContext
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment