// // ImageProcess.swift // KdanAuto // // Created by 朱东勇 on 2023/2/7. // import Foundation import AppKit import CoreImage import CryptoKit class ImageProcess : NSObject { // Image compare class func compareJPEG(_ resultPath:String, checkPath:String, processCover:Bool) -> Double { autoreleasepool { if !FileManager.default.fileExists(atPath: resultPath) || !FileManager.default.fileExists(atPath: checkPath) { return -1 } let rImage = NSImage.init(contentsOfFile: resultPath) ?? nil let cImage = NSImage.init(contentsOfFile: checkPath) ?? nil if nil == rImage || nil == cImage { return -1 } let resultImage = rImage! let checkImage = cImage! var resultImageRep = NSBitmapImageRep.init(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) let checkImageRep = NSBitmapImageRep.init(cgImage: checkImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) var rWidth = resultImageRep.pixelsWide var rHeight = resultImageRep.pixelsHigh // let rBitPerPixel = resultImageRep.bitsPerPixel / 8 // let rBytePerRow = resultImageRep.bytesPerRow let cWidth = checkImageRep.pixelsWide let cHeight = checkImageRep.pixelsHigh // let cBitPerPixel = checkImageRep.bitsPerPixel / 8 // let cBytePerRow = checkImageRep.bytesPerRow // 图像为等比例、且长宽比相同 if rHeight*cHeight > 0 && fabs(Double(rWidth-cWidth)) > 1 && fabs(Double(rWidth)/Double(rHeight) - Double(cWidth)/Double(cHeight)) < 0.001 { var ciImage = CIImage(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) ciImage = ciImage.transformed(by: CGAffineTransformMakeScale(Double(cWidth)/Double(rWidth), Double(cHeight)/Double(rHeight))) let context = CIContext() let cgImage = context.createCGImage(ciImage, from: ciImage.extent) if cgImage != nil { resultImageRep = NSBitmapImageRep(cgImage: cgImage!); rWidth = resultImageRep.pixelsWide rHeight = resultImageRep.pixelsHigh } } let maxWidth = min(rWidth, cWidth) - 1 let maxHeight = min(rHeight, cHeight) - 1 // check background color // 挑选图片 对角斜线 上的相素进行识别 let markInfo = NSMutableDictionary.init() for w in 0...maxWidth { let x = Int(w) for mark in 0...1 { let color = checkImageRep.colorAt(x: min(x, cWidth-1), y: min(mark == 0 ? x : (maxWidth - x), (cHeight-1))) as! NSColor if (markInfo[color] != nil) { let count = (markInfo[color] as? NSNumber)!.intValue markInfo[color] = NSNumber.init(value: count + 1) }else { markInfo[color] = NSNumber.init(value: 1) } } } var maxCount = Int(0); var bgColor : NSColor = NSColor.clear for color in markInfo.allKeys { let count = (markInfo[color] as? NSNumber)!.intValue if count > maxCount { maxCount = count bgColor = color as! NSColor } } // if nil != bgColor { // NSLog(String("识别到背景色\(bgColor)")) // } // let bg_r = Int(bgColor.redComponent*255); // let bg_g = Int(bgColor.redComponent*255); // let bg_b = Int(bgColor.redComponent*255); // let bg_a = Int(bgColor.redComponent*255); // let data = NSMutableData.init(length: cWidth * cHeight * 4) // Compare let compareDifValue = DataModel.shared.comparativeDifference() var equalCount = 0 as Double var bgCount = 0 as Double let semaphore = DispatchSemaphore(value: 0) let operateQueue = OperationQueue() operateQueue.maxConcurrentOperationCount = 20; var processCount = Int(0) for w in 0...maxWidth { let x = Int(w) operateQueue.addOperation { autoreleasepool { for h in 0...maxHeight { let y = Int(h) objc_sync_enter(self) let cColor = checkImageRep.colorAt(x: x, y: y) as! NSColor let rColor = resultImageRep.colorAt(x: x, y: y) as! NSColor objc_sync_exit(self) let cr = Int(cColor.redComponent*255) let cg = Int(cColor.greenComponent*255) let cb = Int(cColor.blueComponent*255) let ca = Int(cColor.blueComponent*255) let rr = Int(rColor.redComponent*255) let rg = Int(rColor.greenComponent*255) let rb = Int(rColor.blueComponent*255) let ra = Int(rColor.blueComponent*255) // if (cColor.isEqual(to: rColor)) { if (abs(cr - rr) <= compareDifValue && abs(cg - rg) <= compareDifValue && abs(cb - rb) <= compareDifValue && abs(ca - ra) <= compareDifValue) { equalCount = equalCount + 1 if cColor.isEqual(to: bgColor) { objc_sync_enter(self) bgCount += 1 objc_sync_exit(self) } }else if (processCover && nil != data){ let addr = cWidth * 4 * y + x * 4 objc_sync_enter(self) var r = uint8(255) data!.replaceBytes(in: NSRange.init(location: addr+0, length: 1), withBytes: &r) var g = uint8(255 * rColor.greenComponent/2) data!.replaceBytes(in: NSRange.init(location: addr+1, length: 1), withBytes: &g) var b = uint8(255 * rColor.blueComponent/2) data!.replaceBytes(in: NSRange.init(location: addr+2, length: 1), withBytes: &b) var a = uint8(255 * rColor.alphaComponent * 0.7) data!.replaceBytes(in: NSRange.init(location: addr+3, length: 1), withBytes: &a) objc_sync_exit(self) } } processCount = processCount+1 if (processCount >= maxWidth) { semaphore.signal() } } } } semaphore.wait() let outDegree = Double(max(equalCount-bgCount, 1)/(max(Double(cWidth) * Double(cHeight)-bgCount, 1)) * 100.0) if (abs(outDegree - 100) > 0 && processCover && nil != data) { DispatchQueue.global().async { autoreleasepool { let cfData = CFDataCreate(kCFAllocatorDefault, data?.bytes, data!.length) let dataProvider = CGDataProvider.init(data: cfData!) let colorSpace = CGColorSpaceCreateDeviceRGB() let cgImage = CGImage.init(width: cWidth, height: cHeight, bitsPerComponent: 8, bitsPerPixel: 8 * 4, bytesPerRow: cWidth * 4, space: colorSpace, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrderDefault.rawValue) ?? CGBitmapInfo.byteOrderDefault, provider: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) if nil != cgImage { let coverPath = NSString(format: "%@_cover.png", NSString(string: resultPath).deletingPathExtension) as! String let rep = NSBitmapImageRep.init(cgImage: cgImage!) if let saveData = rep.representation(using: .png, properties: [:]) { let url = URL.init(fileURLWithPath: coverPath, isDirectory: false) try? saveData.write(to: url) } } } } } NSLog(String("过滤点数目\(bgCount)")) return outDegree } } class func compareJPEG(_ resultPath:String, checkPath:String, processCover:Bool, complention:@escaping (_ degree:Double) -> ()) { autoreleasepool { if !FileManager.default.fileExists(atPath: resultPath) || !FileManager.default.fileExists(atPath: checkPath) { complention(-1) return } let rImage = NSImage.init(contentsOfFile: resultPath) ?? nil let cImage = NSImage.init(contentsOfFile: checkPath) ?? nil if nil == rImage || nil == cImage { complention(-1) return } if (ImageProcess.checkEqualFor(resultPath, checkPath: checkPath)) { complention(100) return } let resultImage = rImage! let checkImage = cImage! var resultImageRep = NSBitmapImageRep.init(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) let checkImageRep = NSBitmapImageRep.init(cgImage: checkImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) var rWidth = resultImageRep.pixelsWide var rHeight = resultImageRep.pixelsHigh // let rBitPerPixel = resultImageRep.bitsPerPixel / 8 // let rBytePerRow = resultImageRep.bytesPerRow let cWidth = checkImageRep.pixelsWide let cHeight = checkImageRep.pixelsHigh // let cBitPerPixel = checkImageRep.bitsPerPixel / 8 // let cBytePerRow = checkImageRep.bytesPerRow // 图像为等比例、且长宽比相同 if rHeight*cHeight > 0 && fabs(Double(rWidth-cWidth)) > 1 && fabs(Double(rWidth)/Double(rHeight) - Double(cWidth)/Double(cHeight)) < 0.001 { var ciImage = CIImage(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) ciImage = ciImage.transformed(by: CGAffineTransformMakeScale(Double(cWidth)/Double(rWidth), Double(cHeight)/Double(rHeight))) let context = CIContext() let cgImage = context.createCGImage(ciImage, from: ciImage.extent) if cgImage != nil { resultImageRep = NSBitmapImageRep(cgImage: cgImage!); rWidth = resultImageRep.pixelsWide rHeight = resultImageRep.pixelsHigh } } let maxWidth = min(rWidth, cWidth) - 1 let maxHeight = min(rHeight, cHeight) - 1 if (maxWidth == 0 || maxHeight == 0) { complention(-1) return } // check background color // 挑选图片 对角斜线 上的相素进行识别 let markInfo = NSMutableDictionary.init() for w in 0...maxWidth { let x = Int(w) for mark in 0...1 { let color = checkImageRep.colorAt(x: min(x, cWidth-1), y: min(mark == 0 ? x : (maxWidth - x), (cHeight-1))) as! NSColor if (markInfo[color] != nil) { let count = (markInfo[color] as? NSNumber)!.intValue markInfo[color] = NSNumber.init(value: count + 1) }else { markInfo[color] = NSNumber.init(value: 1) } } } var maxCount = Int(0); var bgColor : NSColor = NSColor.clear for color in markInfo.allKeys { let count = (markInfo[color] as? NSNumber)!.intValue if count > maxCount { maxCount = count bgColor = color as! NSColor } } // if nil != bgColor { // NSLog(String("识别到背景色\(bgColor)")) // } // let bg_r = Int(bgColor.redComponent*255); // let bg_g = Int(bgColor.redComponent*255); // let bg_b = Int(bgColor.redComponent*255); // let bg_a = Int(bgColor.redComponent*255); // let data = NSMutableData.init(length: cWidth * cHeight * 4) // Compare let compareDifValue = DataModel.shared.comparativeDifference() var equalCount = 0 as Double var bgCount = 0 as Double var processCount = Int(0) for w in 0...maxWidth { let x = Int(w) for h in 0...maxHeight { let y = Int(h) let cColor = checkImageRep.colorAt(x: x, y: y) as! NSColor let rColor = resultImageRep.colorAt(x: x, y: y) as! NSColor let cr = Int(cColor.redComponent*255) let cg = Int(cColor.greenComponent*255) let cb = Int(cColor.blueComponent*255) let ca = Int(cColor.blueComponent*255) let rr = Int(rColor.redComponent*255) let rg = Int(rColor.greenComponent*255) let rb = Int(rColor.blueComponent*255) let ra = Int(rColor.blueComponent*255) // if (cColor.isEqual(to: rColor)) { if (abs(cr - rr) <= compareDifValue && abs(cg - rg) <= compareDifValue && abs(cb - rb) <= compareDifValue && abs(ca - ra) <= compareDifValue) { equalCount = equalCount + 1 if cColor.isEqual(to: bgColor) { bgCount += 1 } }else if (processCover && nil != data){ let addr = cWidth * 4 * y + x * 4 var r = uint8(255) data!.replaceBytes(in: NSRange.init(location: addr+0, length: 1), withBytes: &r) var g = uint8(255 * rColor.greenComponent/2) data!.replaceBytes(in: NSRange.init(location: addr+1, length: 1), withBytes: &g) var b = uint8(255 * rColor.blueComponent/2) data!.replaceBytes(in: NSRange.init(location: addr+2, length: 1), withBytes: &b) var a = uint8(255 * rColor.alphaComponent * 0.7) data!.replaceBytes(in: NSRange.init(location: addr+3, length: 1), withBytes: &a) } } } let outDegree = Double(max(equalCount-bgCount, 1)/(max(Double(cWidth) * Double(cHeight)-bgCount, 1)) * 100.0) if (abs(outDegree - 100) > 0 && processCover && nil != data) { DispatchQueue.global().async { autoreleasepool { let cfData = CFDataCreate(kCFAllocatorDefault, data?.bytes, data!.length) let dataProvider = CGDataProvider.init(data: cfData!) let colorSpace = CGColorSpaceCreateDeviceRGB() let cgImage = CGImage.init(width: cWidth, height: cHeight, bitsPerComponent: 8, bitsPerPixel: 8 * 4, bytesPerRow: cWidth * 4, space: colorSpace, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrderDefault.rawValue) ?? CGBitmapInfo.byteOrderDefault, provider: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) if nil != cgImage { let coverPath = NSString(format: "%@_cover.png", NSString(string: resultPath).deletingPathExtension) as! String let rep = NSBitmapImageRep.init(cgImage: cgImage!) if let saveData = rep.representation(using: .png, properties: [:]) { let url = URL.init(fileURLWithPath: coverPath, isDirectory: false) try? saveData.write(to: url) } } } } } DispatchQueue.global().async { autoreleasepool { NSLog(String("过滤点数目\(bgCount)")) complention(outDegree) } } } } //Genera Image class func processImage(_ resultPath:String, checkPath:String) -> NSImage? { autoreleasepool { if !FileManager.default.fileExists(atPath: resultPath) || !FileManager.default.fileExists(atPath: checkPath) { return nil } let rImage = NSImage.init(contentsOfFile: resultPath) ?? nil let cImage = NSImage.init(contentsOfFile: checkPath) ?? nil if nil == rImage || nil == cImage { return nil } if (ImageProcess.checkEqualFor(resultPath, checkPath: checkPath)) { return nil } let resultImage = rImage as! NSImage let checkImage = cImage as! NSImage let resultImageRep = NSBitmapImageRep.init(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) let checkImageRep = NSBitmapImageRep.init(cgImage: checkImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!) let rWidth = resultImageRep.pixelsWide let rHeight = resultImageRep.pixelsHigh let rBitPerPixel = resultImageRep.bitsPerPixel / 8 let rBytePerRow = resultImageRep.bytesPerRow let cWidth = checkImageRep.pixelsWide let cHeight = checkImageRep.pixelsHigh let cBitPerPixel = checkImageRep.bitsPerPixel / 8 let cBytePerRow = checkImageRep.bytesPerRow let data = NSMutableData.init(length: cWidth * cHeight * 4) if nil == data { return nil } let maxWidth = min(rWidth, cWidth) - 1 let maxHeight = min(rHeight, cHeight) - 1 // check background color // 挑选图片 对角斜线 上的相素进行识别 var markInfo = NSMutableDictionary.init() for w in 0...maxWidth { let x = Int(w) for mark in 0...1 { let color = checkImageRep.colorAt(x: min(x, cWidth-1), y: min(mark == 0 ? x : (maxWidth - x), (cHeight-1))) as! NSColor if (markInfo[color] != nil) { let count = (markInfo[color] as? NSNumber)!.intValue ?? 0 markInfo[color] = NSNumber.init(value: count + 1) }else { markInfo[color] = NSNumber.init(value: 1) } } } var maxCount = Int(0); var bgColor : NSColor? = nil for color in markInfo.allKeys { let count = (markInfo[color] as? NSNumber)!.intValue ?? 0 if count > maxCount { maxCount = count bgColor = color as! NSColor } } if nil != bgColor { NSLog(String("识别到背景色\(bgColor)")) } // Compare let compareDifValue = DataModel.shared.comparativeDifference() for w in 0...maxWidth { let x = Int(w) for h in 0...maxHeight { let y = Int(h) let cColor = checkImageRep.colorAt(x: x, y: y) as! NSColor let rColor = resultImageRep.colorAt(x: x, y: y) as! NSColor let cr = Int(cColor.redComponent*255) let cg = Int(cColor.greenComponent*255) let cb = Int(cColor.blueComponent*255) let ca = Int(cColor.blueComponent*255) let rr = Int(rColor.redComponent*255) let rg = Int(rColor.greenComponent*255) let rb = Int(rColor.blueComponent*255) let ra = Int(rColor.blueComponent*255) // if (cColor.isEqual(to: rColor)) { if (abs(cr - rr) <= compareDifValue && abs(cg - rg) <= compareDifValue && abs(cb - rb) <= compareDifValue && abs(ca - ra) <= compareDifValue) { if bgColor != nil && cColor.isEqual(to: bgColor) { } }else { // NSLog("(\(cr),\(cg),\(cb),\(cr))=(\(rr),\(rg),\(rb),\(rr))") let addr = cWidth * 4 * y + x * 4 var r = uint8(255) data!.replaceBytes(in: NSRange.init(location: addr+0, length: 1), withBytes: &r) var g = uint8(255 * rColor.greenComponent/2) data!.replaceBytes(in: NSRange.init(location: addr+1, length: 1), withBytes: &g) var b = uint8(255 * rColor.blueComponent/2) data!.replaceBytes(in: NSRange.init(location: addr+2, length: 1), withBytes: &b) var a = uint8(255 * rColor.alphaComponent * 0.7) data!.replaceBytes(in: NSRange.init(location: addr+3, length: 1), withBytes: &a) } } } let cfData = CFDataCreate(kCFAllocatorDefault, data?.bytes, data!.length) let dataProvider = CGDataProvider.init(data: cfData!) let colorSpace = CGColorSpaceCreateDeviceRGB() let cgImage = CGImage.init(width: cWidth, height: cHeight, bitsPerComponent: 8, bitsPerPixel: 8 * 4, bytesPerRow: cWidth * 4, space: colorSpace, bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrderDefault.rawValue) ?? CGBitmapInfo.byteOrderDefault, provider: dataProvider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent) if nil != cgImage { return NSImage.init(cgImage: cgImage!, size: NSSize.init(width: cWidth, height: cHeight)) } return nil } } class func checkEqualFor(_ resultPath:String, checkPath:String) -> Bool { // 先通过文件属性 + MD5 码进行快速对比,排除大部分,已经相同的文件 let rfs = try! FileManager.default.attributesOfItem(atPath: resultPath)[FileAttributeKey.size] as! NSNumber let cfs = try! FileManager.default.attributesOfItem(atPath: checkPath)[FileAttributeKey.size] as! NSNumber if rfs.int64Value != cfs.int64Value { //文件大小不一致,反回不一致 return false } let rmd5 = ImageProcess.md5StringOfPath(resultPath); let cmd5 = ImageProcess.md5StringOfPath(checkPath); if (nil != rmd5 && nil != cmd5 && NSString(string: rmd5!).isEqual(to: cmd5!)) { // 文件大小一致,且文件 MD5码完全一致 return true; } return false } class func md5StringOfPath(_ path:String) -> String? { if FileManager.default.fileExists(atPath: path) { let data = NSData(contentsOfFile: path); if (nil == data) { return nil; } return String(format: "%X", data.hashValue); } return nil; } }