KMPDFSignatureImageView.swift 19 KB

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