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 }