// // KMPDFSignatureImageView.swift // PDF Reader Pro // // Created by lizhe on 2023/10/9. // import Cocoa import Quartz let KMSignatureMaxWidth = 400.0 let KMSignatureMaxHeight = 300.0 @objcMembers class KMPDFSignatureImageView: NSView { var picImage: NSImage? @IBOutlet var pictureView: NSView! // @IBOutlet var emptyTipLbl: NSTextField! @IBOutlet var dragButton: NSButton! // @IBOutlet var emptyImg: NSImageView! @IBOutlet var heraLabel: NSTextField! @IBOutlet var orLabel: NSTextField! var clearBackground: Bool = false { didSet { if let imageURL = imageURL, imageURL.path.count > 0 { loadImageViewPath(imageURL, isRemoveBGColor: clearBackground) } } } var trackingArea: NSTrackingArea? var imageURL: URL? var changeSignatureImageCallback: ((Bool) -> Void)? required init?(coder: NSCoder) { super.init(coder: coder) wantsLayer = true layer?.borderWidth = 1.0 layer?.borderColor = NSColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.05).cgColor layer?.backgroundColor = NSColor(red: 1, green: 1, blue: 1, alpha: 0.9).cgColor registerForDraggedTypes([.fileURL]) } override init(frame frameRect: NSRect) { super.init(frame: frameRect) registerForDraggedTypes([.fileURL]) } override func awakeFromNib() { super.awakeFromNib() // emptyTipLbl.stringValue = NSLocalizedString("Select image file", comment: "") // emptyTipLbl.textColor = .labelColor self.heraLabel.stringValue = NSLocalizedString("Drop image here", comment: "") self.orLabel.stringValue = NSLocalizedString("or", comment: "") dragButton.title = NSLocalizedString("Select a File", comment: "") dragButton.wantsLayer = true dragButton.setTitleColor(color: .labelColor, font: nil) pictureView.wantsLayer = true // emptyImg.image = NSImage(named: "signPicture_nor") trackingArea = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways], owner: self, userInfo: nil) if let trackingArea = trackingArea { addTrackingArea(trackingArea) } } func supportedImageTypes() -> [String] { return ["jpg", "cur", "bmp", "jpeg", "gif", "png", "tiff", "tif", "ico", "icns", "tga", "psd", "eps", "hdr", "jp2", "jpc", "pict", "sgi", "pdf"] } override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) if let picImage = picImage { let selfSize = self.frame.size var rect = NSZeroRect if let imageCIImage = CIImage(data: picImage.tiffRepresentation!) { let size = imageCIImage.extent.size let scale = min((selfSize.width - 30) / size.width, (selfSize.height - 30) / size.height) if scale > 1 { rect = NSRect(x: 0, y: 0, width: size.width, height: size.height) } else { rect = NSRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale) } picImage.draw(in: NSRect(x: (selfSize.width - rect.size.width) / 2, y: (selfSize.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height), from: NSZeroRect, operation: .sourceOver, fraction: 1.0) } } } func clearImage() { picImage = nil imageURL = nil pictureView.isHidden = false // emptyTipLbl.isHidden = false needsDisplay = true } func reSelectImage() { buttonItemClick_SelectPhoto(nil) } func signatureImage() -> NSImage? { var rect = CGRect.zero guard let picImage = self.picImage else { return nil } let imageCIImage = CIImage(data: picImage.tiffRepresentation!) let size = imageCIImage?.extent.size ?? CGSize.zero let scale = min(KMSignatureMaxWidth / size.width, KMSignatureMaxHeight / size.height) if scale > 1 { rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) } else { rect = CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale) } let image = NSImage(size: rect.size) image.lockFocus() picImage.draw(in: rect, from: NSZeroRect, operation: .sourceOver, fraction: 1.0) image.unlockFocus() return image } func loadImageViewPath(_ url: URL, isRemoveBGColor: Bool) { self.imageURL = url let filePath: NSString = url.path as NSString if filePath.pathExtension.lowercased() == "pdf" { if let pdf = PDFDocument(url: url), pdf.isEncrypted { return } } if let image = NSImage(contentsOfFile: filePath as String) { if isRemoveBGColor { // if let imageData = image.tiffRepresentation { // if let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil), // let imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) { // // let imageWidth = image.size.width // let imageHeight = image.size.height // let bytesPerRow = Int(imageWidth) * 4 // var rgbImageBuf = [UInt32](repeating: 0, count: Int(bytesPerRow) * Int(imageHeight)) // // let colorSpace = CGColorSpaceCreateDeviceRGB() // let imageRect = CGRect(x: 0.0, y: 0.0, width: imageWidth, height: imageHeight) // // if let context = CGContext(data: &rgbImageBuf, // width: Int(imageWidth), // height: Int(imageHeight), // bitsPerComponent: 8, // bytesPerRow: bytesPerRow, // space: colorSpace, // bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue), // let dataProvider = CGDataProvider(dataInfo: nil, // data: &rgbImageBuf, // size: Int(bytesPerRow) * Int(imageHeight), // releaseData: { (info, data, size) in // data.deallocate() // }), // let newImageRef = CGImage(width: Int(imageWidth), // height: Int(imageHeight), // bitsPerComponent: 8, // bitsPerPixel: 32, // bytesPerRow: bytesPerRow, // space: colorSpace, // bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue), // provider: dataProvider, // decode: nil, // shouldInterpolate: true, // intent: .defaultIntent) { // // let newImage = NSImage(size: CGSize(width: imageWidth, height: imageHeight)) // newImage.lockFocus() // // let imageContext = NSGraphicsContext(cgContext: context, flipped: false) // NSGraphicsContext.current = imageContext // context.draw(imageRef, in: imageRect) // newImage.unlockFocus() // self.picImage = newImage // self.pictureView.isHidden = true // self.emptyTipLbl.isHidden = true // self.setNeedsDisplay(NSRect.zero) // // } // } // } let resultImage = self.makeImageTransparent(image) self.picImage = resultImage self.pictureView.isHidden = true // self.emptyTipLbl.isHidden = true self.layer?.setNeedsDisplay() debugPrint("移除成功") } else { self.picImage = image self.pictureView.isHidden = true // self.emptyTipLbl.isHidden = true self.layer?.setNeedsDisplay() } } } func makeImageTransparent(_ inputImage: NSImage) -> NSImage? { if let cgImage = inputImage.cgImage(forProposedRect: nil, context: nil, hints: nil) { // 创建一个位图上下文,与图像大小相同 let width = cgImage.width let height = cgImage.height let colorSpace = CGColorSpaceCreateDeviceRGB() let bytesPerPixel = 4 let bytesPerRow = bytesPerPixel * width let bitsPerComponent = 8 let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) // 绘制图像到上下文 context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) // 获取上下文中的像素数据 if let data = context?.data { let buffer = data.bindMemory(to: UInt8.self, capacity: width * height * bytesPerPixel) // 迭代每个像素并将白色像素变为透明 for y in 0.. 240 && green > 240 && blue > 240 { // 将白色像素的 alpha 通道设为0,使其变为透明 buffer[pixelIndex + 3] = 0 } } } // 创建新的 CGImage if let newCgImage = context?.makeImage() { // 创建新的 NSImage let newImage = NSImage(cgImage: newCgImage, size: inputImage.size) return newImage // 现在,newImage 包含具有白色像素变为透明的图像 // 可以将其显示在您的应用程序中或保存到磁盘上 } } } return nil } func setGradualChanging() -> CAGradientLayer { let gradientLayer = CAGradientLayer() gradientLayer.frame = dragButton.bounds gradientLayer.colors = [NSColor.lightGray.cgColor, NSColor.white.cgColor] gradientLayer.startPoint = CGPoint(x: 0, y: 0) gradientLayer.endPoint = CGPoint(x: 1, y: 1) gradientLayer.locations = [0, 1] return gradientLayer } func isDamageImage(_ image: NSImage, imagePath: String) -> Bool { let addImageAnnotation = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last?.appending("/\(Bundle.main.bundleIdentifier!)") ?? "" if !FileManager.default.fileExists(atPath: addImageAnnotation) { try? FileManager.default.createDirectory(atPath: addImageAnnotation, withIntermediateDirectories: false, attributes: nil) } if let imageData = image.tiffRepresentation, let imageRep = NSBitmapImageRep(data: imageData) { imageRep.size = image.size var imageData: Data? if imagePath.lowercased() == "png" { imageData = imageRep.representation(using: .png, properties: [:]) } else { imageData = imageRep.representation(using: .jpeg, properties: [:]) } if let imageData = imageData, let tagString = tagString() { let rPath = (addImageAnnotation.stringByAppendingPathComponent(tagString)).stringByAppendingPathExtension("png") do { try imageData.write(to: URL(fileURLWithPath: rPath)) // NSWorkspace.shared.selectFile(savePanel.url?.path, inFileViewerRootedAtPath: "") return false } catch { return true } } } return false } func tagString() -> String? { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyMMddHHmmss" return String(format: "%@%04d", dateFormatter.string(from: Date()), Int(arc4random_uniform(10000))) } @IBAction func buttonItemClick_SelectPhoto(_ sender: Any?) { let openPanel = NSOpenPanel() openPanel.allowedFileTypes = supportedImageTypes() if let accessoryView = openPanel.accessoryView as? NSButton { accessoryView.state = UserDefaults.standard.bool(forKey: "AnnotationSelectPhoto") ? .on : .off } openPanel.allowsMultipleSelection = false openPanel.beginSheetModal(for: window ?? NSApp.mainWindow!) { [weak self] (result) in if result == .OK, let url = openPanel.url { // Check if the PDF file is damaged let filePath: NSString = url.path as NSString if filePath.pathExtension.lowercased() == "pdf" { if let pdf = PDFDocument(url: url), !pdf.isEncrypted { self?.loadImageViewPath(url, isRemoveBGColor: self?.clearBackground ?? false) self?.changeSignatureImageCallback?(true) return } else { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "") alert.runModal() return } } if let image = NSImage(contentsOfFile: url.path), self?.isDamageImage(image, imagePath: url.path) == true { let alert = NSAlert() alert.alertStyle = .critical alert.messageText = String(format: NSLocalizedString("The file \"%@\" could not be opened.", comment: ""), url.path.lastPathComponent) alert.informativeText = NSLocalizedString("It may be damaged or use a file format that PDF Reader Pro doesn’t recognize.", comment: "") alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "")) alert.beginSheetModal(for: NSApp.mainWindow ?? NSApp.keyWindow!) { (returnCode) in if returnCode == .alertFirstButtonReturn { } } return } self?.loadImageViewPath(url, isRemoveBGColor: self?.clearBackground ?? false) self?.changeSignatureImageCallback?(true) } } } override func mouseEntered(with event: NSEvent) { super.mouseEntered(with: event) // emptyImg.image = NSImage(named: "signPicture_hover") } override func mouseMoved(with event: NSEvent) { super.mouseMoved(with: event) if let windowContentView = window?.contentView, let convertPoint = pictureView?.convert(event.locationInWindow, from: windowContentView) { if pictureView.bounds.contains(convertPoint) { // emptyImg.image = NSImage(named: "signPicture_hover") } else { // emptyImg.image = NSImage(named: "signPicture_nor") } } } override func mouseExited(with event: NSEvent) { super.mouseExited(with: event) // emptyImg.image = NSImage(named: "signPicture_nor") } override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation { let pboard = sender.draggingPasteboard if pboard.availableType(from: [.fileURL]) != nil { guard let pbItems = pboard.pasteboardItems else { return NSDragOperation(rawValue: 0) } for item in pbItems { guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else { continue } if supportedImageTypes().contains(_url.pathExtension.lowercased()) { return .every } } } return [] } override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool { let pboard = sender.draggingPasteboard if pboard.availableType(from: [.fileURL]) != nil { guard let pbItems = pboard.pasteboardItems else { return false } for item in pbItems { guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else { continue } if supportedImageTypes().contains(_url.pathExtension.lowercased()) { loadImageViewPath(_url, isRemoveBGColor: clearBackground) changeSignatureImageCallback?(true) return true } } } return false } } private extension NSImage { func resized(to size: NSSize) -> NSImage { let image = NSImage(size: size) image.lockFocus() let ctx = NSGraphicsContext.current?.cgContext ctx?.interpolationQuality = .high draw(in: NSRect(origin: .zero, size: size), from: NSRect(origin: .zero, size: self.size), operation: .sourceOver, fraction: 1.0) image.unlockFocus() return image } }