Skip to content

Instantly share code, notes, and snippets.

@finestructure
Created February 23, 2021 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save finestructure/84b0b58b567e857b4f38c0e07882eecf to your computer and use it in GitHub Desktop.
Save finestructure/84b0b58b567e857b4f38c0e07882eecf to your computer and use it in GitHub Desktop.
NSImage diff speed test
import Cocoa
func context(for cgImage: CGImage) -> CGContext? {
guard
let space = cgImage.colorSpace,
let context = CGContext(
data: nil,
width: cgImage.width,
height: cgImage.height,
bitsPerComponent: cgImage.bitsPerComponent,
bytesPerRow: cgImage.bytesPerRow,
space: space,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
)
else { return nil }
context.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
return context
}
private func NSImagePNGRepresentation(_ image: NSImage) -> Data? {
guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
let rep = NSBitmapImageRep(cgImage: cgImage)
rep.size = image.size
return rep.representation(using: .png, properties: [:])
}
func compare(_ old: NSImage, _ new: NSImage, precision: Float) -> Bool {
guard let oldCgImage = old.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
guard let newCgImage = new.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
guard oldCgImage.width != 0 else { return false }
guard newCgImage.width != 0 else { return false }
guard oldCgImage.width == newCgImage.width else { return false }
guard oldCgImage.height != 0 else { return false }
guard newCgImage.height != 0 else { return false }
guard oldCgImage.height == newCgImage.height else { return false }
guard let oldContext = context(for: oldCgImage) else { return false }
guard let newContext = context(for: newCgImage) else { return false }
guard let oldData = oldContext.data else { return false }
guard let newData = newContext.data else { return false }
let byteCount = oldContext.height * oldContext.bytesPerRow
if memcmp(oldData, newData, byteCount) == 0 { return true }
let newer = NSImage(data: NSImagePNGRepresentation(new)!)!
guard let newerCgImage = newer.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
guard let newerContext = context(for: newerCgImage) else { return false }
guard let newerData = newerContext.data else { return false }
if memcmp(oldData, newerData, byteCount) == 0 { return true }
if precision >= 1 { return false }
let oldRep = NSBitmapImageRep(cgImage: oldCgImage)
let newRep = NSBitmapImageRep(cgImage: newerCgImage)
var differentPixelCount = 0
let pixelCount = oldRep.pixelsWide * oldRep.pixelsHigh
let threshold = 1 - precision
for x in 0..<oldRep.pixelsWide {
for y in 0..<oldRep.pixelsHigh {
if oldRep.colorAt(x: x, y: y) != newRep.colorAt(x: x, y: y) { differentPixelCount += 1 }
if Float(differentPixelCount) / Float(pixelCount) > threshold { return false}
}
}
return true
}
func compare2(_ old: NSImage, _ new: NSImage, precision: Float) -> Bool {
guard let oldCgImage = old.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
guard let newCgImage = new.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
guard oldCgImage.width != 0 else { return false }
guard newCgImage.width != 0 else { return false }
guard oldCgImage.width == newCgImage.width else { return false }
guard oldCgImage.height != 0 else { return false }
guard newCgImage.height != 0 else { return false }
guard oldCgImage.height == newCgImage.height else { return false }
guard let oldContext = context(for: oldCgImage) else { return false }
guard let newContext = context(for: newCgImage) else { return false }
guard let oldData = oldContext.data else { return false }
guard let newData = newContext.data else { return false }
let byteCount = oldContext.height * oldContext.bytesPerRow
if memcmp(oldData, newData, byteCount) == 0 { return true }
let newer = NSImage(data: NSImagePNGRepresentation(new)!)!
guard let newerCgImage = newer.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return false }
guard let newerContext = context(for: newerCgImage) else { return false }
guard let newerData = newerContext.data else { return false }
if memcmp(oldData, newerData, byteCount) == 0 { return true }
if precision >= 1 { return false }
let oldRep = NSBitmapImageRep(cgImage: oldCgImage)
let newRep = NSBitmapImageRep(cgImage: newerCgImage)
var differentPixelCount = 0
let pixelCount = oldRep.pixelsWide * oldRep.pixelsHigh
let threshold = (1 - precision) * Float(pixelCount)
let p1: UnsafeMutablePointer<UInt8> = oldRep.bitmapData!
let p2: UnsafeMutablePointer<UInt8> = newRep.bitmapData!
for offset in 0 ..< pixelCount {
if p1[offset] != p2[offset]
|| p1[offset + 1] != p2[offset + 1]
|| p1[offset + 2] != p2[offset + 2]
|| p1[offset + 3] != p2[offset + 3] {
differentPixelCount += 1
}
if Float(differentPixelCount) > threshold { return false }
}
return true
}
let orig = NSImage(contentsOfFile: "/Users/sas/Downloads/orig.png")!
let new = NSImage(contentsOfFile: "/Users/sas/Downloads/new.png")!
// compare
do {
let start = Date()
print(compare(orig, new, precision: 1))
let elapsed = Date().timeIntervalSince(start)
print("compare1, precision 1 : \(elapsed)")
}
do {
let start = Date()
print(compare(orig, new, precision: 0.999))
let elapsed = Date().timeIntervalSince(start)
print("compare1, precision 0.999: \(elapsed)")
}
// compare2
do {
let start = Date()
print(compare2(orig, new, precision: 1))
let elapsed = Date().timeIntervalSince(start)
print("compare2, precision 1 : \(elapsed)")
}
do {
let start = Date()
print(compare2(orig, new, precision: 0.999))
let elapsed = Date().timeIntervalSince(start)
print("compare2, precision 0.999: \(elapsed)")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment