KMPDFSignatureImageView.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. //
  2. // KMPDFSignatureImageView.swift
  3. // PDF Master
  4. //
  5. // Created by lizhe on 2023/10/9.
  6. //
  7. import Cocoa
  8. import Quartz
  9. let KMSignatureMaxWidth = 400.0
  10. let KMSignatureMaxHeight = 300.0
  11. @objcMembers class KMPDFSignatureImageView: NSView {
  12. var picImage: NSImage?
  13. @IBOutlet var pictureView: NSView!
  14. @IBOutlet var emptyTipLbl: NSTextField!
  15. @IBOutlet var dragButton: NSButton!
  16. @IBOutlet var emptyImg: NSImageView!
  17. var clearBackground: Bool = false {
  18. didSet {
  19. if let imageURL = imageURL, imageURL.path.count > 0 {
  20. loadImageViewPath(imageURL, isRemoveBGColor: clearBackground)
  21. }
  22. }
  23. }
  24. var trackingArea: NSTrackingArea?
  25. var imageURL: URL?
  26. var changeSignatureImageCallback: ((Bool) -> Void)?
  27. required init?(coder: NSCoder) {
  28. super.init(coder: coder)
  29. wantsLayer = true
  30. layer?.borderWidth = 1.0
  31. layer?.borderColor = NSColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.05).cgColor
  32. layer?.backgroundColor = NSColor(red: 1, green: 1, blue: 1, alpha: 0.9).cgColor
  33. registerForDraggedTypes([.fileURL])
  34. }
  35. override init(frame frameRect: NSRect) {
  36. super.init(frame: frameRect)
  37. }
  38. override func awakeFromNib() {
  39. super.awakeFromNib()
  40. emptyTipLbl.stringValue = NSLocalizedString("Select image file", comment: "")
  41. emptyTipLbl.textColor = .labelColor
  42. dragButton.wantsLayer = true
  43. dragButton.setTitleColor(color: .labelColor, font: nil)
  44. pictureView.wantsLayer = true
  45. emptyImg.image = NSImage(named: "signPicture_nor")
  46. trackingArea = NSTrackingArea(rect: bounds, options: [.mouseEnteredAndExited, .mouseMoved, .activeAlways], owner: self, userInfo: nil)
  47. if let trackingArea = trackingArea {
  48. addTrackingArea(trackingArea)
  49. }
  50. }
  51. func supportedImageTypes() -> [String] {
  52. return ["jpg", "cur", "bmp", "jpeg", "gif", "png", "tiff", "tif", "ico", "icns", "tga", "psd", "eps", "hdr", "jp2", "jpc", "pict", "sgi", "pdf"]
  53. }
  54. override func draw(_ dirtyRect: NSRect) {
  55. super.draw(dirtyRect)
  56. if let picImage = picImage {
  57. var rect = NSZeroRect
  58. if let imageCIImage = CIImage(data: picImage.tiffRepresentation!) {
  59. let size = imageCIImage.extent.size
  60. let scale = min((dirtyRect.size.width - 30) / size.width, (dirtyRect.size.height - 30) / size.height)
  61. if scale > 1 {
  62. rect = NSRect(x: 0, y: 0, width: size.width, height: size.height)
  63. } else {
  64. rect = NSRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale)
  65. }
  66. picImage.draw(in: NSRect(x: (dirtyRect.size.width - rect.size.width) / 2, y: (dirtyRect.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height), from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
  67. }
  68. }
  69. }
  70. func clearImage() {
  71. picImage = nil
  72. imageURL = nil
  73. pictureView.isHidden = false
  74. emptyTipLbl.isHidden = false
  75. needsDisplay = true
  76. }
  77. func reSelectImage() {
  78. buttonItemClick_SelectPhoto(nil)
  79. }
  80. func signatureImage() -> NSImage? {
  81. var rect = CGRect.zero
  82. guard let picImage = self.picImage else {
  83. return nil
  84. }
  85. let imageCIImage = CIImage(data: picImage.tiffRepresentation!)
  86. let size = imageCIImage?.extent.size ?? CGSize.zero
  87. let scale = min(KMSignatureMaxWidth / size.width, KMSignatureMaxHeight / size.height)
  88. if scale > 1 {
  89. rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
  90. } else {
  91. rect = CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale)
  92. }
  93. let image = NSImage(size: rect.size)
  94. image.lockFocus()
  95. picImage.draw(in: rect, from: NSZeroRect, operation: .sourceOver, fraction: 1.0)
  96. image.unlockFocus()
  97. return image
  98. }
  99. func loadImageViewPath(_ url: URL, isRemoveBGColor: Bool) {
  100. self.imageURL = url
  101. let filePath: NSString = url.path as NSString
  102. if filePath.pathExtension.lowercased() == "pdf" {
  103. if let pdf = PDFDocument(url: url), pdf.isEncrypted {
  104. return
  105. }
  106. }
  107. if let image = NSImage(contentsOfFile: filePath as String) {
  108. if isRemoveBGColor {
  109. // if let imageData = image.tiffRepresentation {
  110. // if let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil),
  111. // let imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
  112. //
  113. // let imageWidth = image.size.width
  114. // let imageHeight = image.size.height
  115. // let bytesPerRow = Int(imageWidth) * 4
  116. // var rgbImageBuf = [UInt32](repeating: 0, count: Int(bytesPerRow) * Int(imageHeight))
  117. //
  118. // let colorSpace = CGColorSpaceCreateDeviceRGB()
  119. // let imageRect = CGRect(x: 0.0, y: 0.0, width: imageWidth, height: imageHeight)
  120. //
  121. // if let context = CGContext(data: &rgbImageBuf,
  122. // width: Int(imageWidth),
  123. // height: Int(imageHeight),
  124. // bitsPerComponent: 8,
  125. // bytesPerRow: bytesPerRow,
  126. // space: colorSpace,
  127. // bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue),
  128. // let dataProvider = CGDataProvider(dataInfo: nil,
  129. // data: &rgbImageBuf,
  130. // size: Int(bytesPerRow) * Int(imageHeight),
  131. // releaseData: { (info, data, size) in
  132. // data.deallocate()
  133. // }),
  134. // let newImageRef = CGImage(width: Int(imageWidth),
  135. // height: Int(imageHeight),
  136. // bitsPerComponent: 8,
  137. // bitsPerPixel: 32,
  138. // bytesPerRow: bytesPerRow,
  139. // space: colorSpace,
  140. // bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.last.rawValue),
  141. // provider: dataProvider,
  142. // decode: nil,
  143. // shouldInterpolate: true,
  144. // intent: .defaultIntent) {
  145. //
  146. // let newImage = NSImage(size: CGSize(width: imageWidth, height: imageHeight))
  147. // newImage.lockFocus()
  148. //
  149. // let imageContext = NSGraphicsContext(cgContext: context, flipped: false)
  150. // NSGraphicsContext.current = imageContext
  151. // context.draw(imageRef, in: imageRect)
  152. // newImage.unlockFocus()
  153. // self.picImage = newImage
  154. // self.pictureView.isHidden = true
  155. // self.emptyTipLbl.isHidden = true
  156. // self.setNeedsDisplay(NSRect.zero)
  157. //
  158. // }
  159. // }
  160. // }
  161. let resultImage = self.makeImageTransparent(image)
  162. self.picImage = resultImage
  163. self.pictureView.isHidden = true
  164. self.emptyTipLbl.isHidden = true
  165. self.layer?.setNeedsDisplay()
  166. debugPrint("移除成功")
  167. } else {
  168. self.picImage = image
  169. self.pictureView.isHidden = true
  170. self.emptyTipLbl.isHidden = true
  171. self.layer?.setNeedsDisplay()
  172. }
  173. }
  174. }
  175. func makeImageTransparent(_ inputImage: NSImage) -> NSImage? {
  176. if let cgImage = inputImage.cgImage(forProposedRect: nil, context: nil, hints: nil) {
  177. // 创建一个位图上下文,与图像大小相同
  178. let width = cgImage.width
  179. let height = cgImage.height
  180. let colorSpace = CGColorSpaceCreateDeviceRGB()
  181. let bytesPerPixel = 4
  182. let bytesPerRow = bytesPerPixel * width
  183. let bitsPerComponent = 8
  184. let context = CGContext(data: nil,
  185. width: width,
  186. height: height,
  187. bitsPerComponent: bitsPerComponent,
  188. bytesPerRow: bytesPerRow,
  189. space: colorSpace,
  190. bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
  191. // 绘制图像到上下文
  192. context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
  193. // 获取上下文中的像素数据
  194. if let data = context?.data {
  195. let buffer = data.bindMemory(to: UInt8.self, capacity: width * height * bytesPerPixel)
  196. // 迭代每个像素并将白色像素变为透明
  197. for y in 0..<height {
  198. for x in 0..<width {
  199. let pixelIndex = (y * width + x) * bytesPerPixel
  200. let red = buffer[pixelIndex]
  201. let green = buffer[pixelIndex + 1]
  202. let blue = buffer[pixelIndex + 2]
  203. // 判断是否是白色像素(这里假设白色为红、绿和蓝都为255)
  204. if red > 240 && green > 240 && blue > 240 {
  205. // 将白色像素的 alpha 通道设为0,使其变为透明
  206. buffer[pixelIndex + 3] = 0
  207. }
  208. }
  209. }
  210. // 创建新的 CGImage
  211. if let newCgImage = context?.makeImage() {
  212. // 创建新的 NSImage
  213. let newImage = NSImage(cgImage: newCgImage, size: inputImage.size)
  214. return newImage
  215. // 现在,newImage 包含具有白色像素变为透明的图像
  216. // 可以将其显示在您的应用程序中或保存到磁盘上
  217. }
  218. }
  219. }
  220. return nil
  221. }
  222. func setGradualChanging() -> CAGradientLayer {
  223. let gradientLayer = CAGradientLayer()
  224. gradientLayer.frame = dragButton.bounds
  225. gradientLayer.colors = [NSColor.lightGray.cgColor, NSColor.white.cgColor]
  226. gradientLayer.startPoint = CGPoint(x: 0, y: 0)
  227. gradientLayer.endPoint = CGPoint(x: 1, y: 1)
  228. gradientLayer.locations = [0, 1]
  229. return gradientLayer
  230. }
  231. func isDamageImage(_ image: NSImage, imagePath: String) -> Bool {
  232. let addImageAnnotation = ((try? FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true))?.appendingPathComponent(Bundle.main.bundleIdentifier ?? ""))?.appendingPathComponent("addImageAnnotation")
  233. if let addImageAnnotation = addImageAnnotation, !FileManager.default.fileExists(atPath: addImageAnnotation.path) {
  234. try? FileManager.default.createDirectory(atPath: addImageAnnotation.path, withIntermediateDirectories: false, attributes: nil)
  235. }
  236. if let imageData = image.tiffRepresentation, let imageRep = NSBitmapImageRep(data: imageData) {
  237. imageRep.size = image.size
  238. var imageData: Data?
  239. if imagePath.lowercased() == "png" {
  240. imageData = imageRep.representation(using: .png, properties: [:])
  241. } else {
  242. imageData = imageRep.representation(using: .jpeg, properties: [:])
  243. }
  244. if let imageData = imageData, let tagString = tagString() {
  245. let rPath = (addImageAnnotation?.appendingPathComponent(tagString))?.appendingPathExtension("png")
  246. if !FileManager.default.createFile(atPath: rPath?.path ?? "", contents: imageData, attributes: nil) {
  247. return true
  248. } else {
  249. return false
  250. }
  251. }
  252. }
  253. return false
  254. }
  255. func tagString() -> String? {
  256. let dateFormatter = DateFormatter()
  257. dateFormatter.dateFormat = "yyMMddHHmmss"
  258. return String(format: "%@%04d", dateFormatter.string(from: Date()), Int(arc4random_uniform(10000)))
  259. }
  260. @IBAction func buttonItemClick_SelectPhoto(_ sender: Any?) {
  261. let openPanel = NSOpenPanel()
  262. openPanel.allowedFileTypes = supportedImageTypes()
  263. if let accessoryView = openPanel.accessoryView as? NSButton {
  264. accessoryView.state = UserDefaults.standard.bool(forKey: "AnnotationSelectPhoto") ? .on : .off
  265. }
  266. openPanel.allowsMultipleSelection = false
  267. openPanel.beginSheetModal(for: window ?? NSApp.mainWindow!) { [weak self] (result) in
  268. if result == .OK, let url = openPanel.url {
  269. // Check if the PDF file is damaged
  270. let filePath: NSString = url.path as NSString
  271. if filePath.pathExtension.lowercased() == "pdf" {
  272. if let pdf = PDFDocument(url: url), !pdf.isEncrypted {
  273. self?.loadImageViewPath(url, isRemoveBGColor: self?.clearBackground ?? false)
  274. self?.changeSignatureImageCallback?(true)
  275. return
  276. } else {
  277. let alert = NSAlert()
  278. alert.alertStyle = .critical
  279. alert.messageText = NSLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", comment: "")
  280. alert.runModal()
  281. return
  282. }
  283. }
  284. if let image = NSImage(contentsOfFile: url.path), self?.isDamageImage(image, imagePath: url.path) == true {
  285. let alert = NSAlert()
  286. alert.alertStyle = .critical
  287. alert.messageText = String(format: NSLocalizedString("The file \"%@\" could not be opened.", comment: ""), url.path.lastPathComponent)
  288. alert.informativeText = NSLocalizedString("It may be damaged or use a file format that PDF Reader Pro doesn’t recognize.", comment: "")
  289. alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
  290. alert.beginSheetModal(for: NSApp.mainWindow ?? NSApp.keyWindow!) { (returnCode) in
  291. if returnCode == .alertFirstButtonReturn {
  292. }
  293. }
  294. return
  295. }
  296. self?.loadImageViewPath(url, isRemoveBGColor: self?.clearBackground ?? false)
  297. self?.changeSignatureImageCallback?(true)
  298. }
  299. }
  300. }
  301. override func mouseEntered(with event: NSEvent) {
  302. super.mouseEntered(with: event)
  303. emptyImg.image = NSImage(named: "signPicture_hover")
  304. }
  305. override func mouseMoved(with event: NSEvent) {
  306. super.mouseMoved(with: event)
  307. if let windowContentView = window?.contentView, let convertPoint = pictureView?.convert(event.locationInWindow, from: windowContentView) {
  308. if pictureView.bounds.contains(convertPoint) {
  309. emptyImg.image = NSImage(named: "signPicture_hover")
  310. } else {
  311. emptyImg.image = NSImage(named: "signPicture_nor")
  312. }
  313. }
  314. }
  315. override func mouseExited(with event: NSEvent) {
  316. super.mouseExited(with: event)
  317. emptyImg.image = NSImage(named: "signPicture_nor")
  318. }
  319. override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
  320. let pboard = sender.draggingPasteboard
  321. if pboard.availableType(from: [.fileURL]) != nil {
  322. if let fileNames = pboard.propertyList(forType: .fileURL) as? [String], fileNames.count == 1 {
  323. if let path: NSString = fileNames.first as NSString?, supportedImageTypes().contains(path.pathExtension.lowercased()) {
  324. return .every
  325. }
  326. }
  327. }
  328. return []
  329. }
  330. override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
  331. let pboard = sender.draggingPasteboard
  332. if pboard.availableType(from: [.fileURL]) != nil {
  333. if let fileNames = pboard.propertyList(forType: .fileURL) as? [String], fileNames.count == 1 {
  334. if let path: NSString = fileNames.first as NSString?, supportedImageTypes().contains(path.pathExtension.lowercased()) {
  335. loadImageViewPath(URL(fileURLWithPath: path as String), isRemoveBGColor: clearBackground)
  336. changeSignatureImageCallback?(true)
  337. return true
  338. }
  339. }
  340. }
  341. return false
  342. }
  343. }
  344. private extension NSImage {
  345. func resized(to size: NSSize) -> NSImage {
  346. let image = NSImage(size: size)
  347. image.lockFocus()
  348. let ctx = NSGraphicsContext.current?.cgContext
  349. ctx?.interpolationQuality = .high
  350. draw(in: NSRect(origin: .zero, size: size), from: NSRect(origin: .zero, size: self.size), operation: .sourceOver, fraction: 1.0)
  351. image.unlockFocus()
  352. return image
  353. }
  354. }