Created
February 23, 2021 11:00
-
-
Save finestructure/84b0b58b567e857b4f38c0e07882eecf to your computer and use it in GitHub Desktop.
NSImage diff speed test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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