KMImageOptimization.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. //
  2. // KMImageOptimization.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by lizhe on 2023/5/25.
  6. //
  7. import AppKit
  8. import CoreGraphics
  9. import CoreImage
  10. class KMImageOptimization: NSObject {
  11. static func compressImageLosslessly(image: NSImage, targetSize: CGSize, maxSizeInBytes: Int, targetCompression: CGFloat) -> Data? {
  12. guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
  13. return nil
  14. }
  15. //宽高自适应
  16. let newSize: CGSize
  17. let widthRatio = targetSize.width / image.size.width
  18. let heightRatio = targetSize.height / image.size.height
  19. if widthRatio > heightRatio {
  20. newSize = CGSize(width: targetSize.height * image.size.width / image.size.height, height: targetSize.height)
  21. } else {
  22. newSize = CGSize(width: targetSize.width, height: targetSize.width * image.size.height / image.size.width)
  23. }
  24. let options: [CFString: Any] = [
  25. kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height),
  26. kCGImageSourceCreateThumbnailFromImageAlways: true,
  27. kCGImageDestinationLossyCompressionQuality: 1.0
  28. ]
  29. guard var imageData = CFDataCreateMutable(nil, 0) else {
  30. return nil
  31. }
  32. guard let destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, nil) else {
  33. return nil
  34. }
  35. CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)
  36. CGImageDestinationFinalize(destination)
  37. var dataSize = CFDataGetLength(imageData)
  38. let targetSizeInBytes = Int(CGFloat(maxSizeInBytes) * targetCompression)
  39. if dataSize <= targetSizeInBytes {
  40. return imageData as Data
  41. }
  42. var compressedData: Data?
  43. let optionsLow: [CFString: Any] = [
  44. kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height) / 2,
  45. kCGImageSourceCreateThumbnailFromImageAlways: true,
  46. kCGImageDestinationLossyCompressionQuality: targetCompression
  47. ]
  48. let optionsHigh: [CFString: Any] = [
  49. kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height),
  50. kCGImageSourceCreateThumbnailFromImageAlways: true,
  51. kCGImageDestinationLossyCompressionQuality: targetCompression
  52. ]
  53. while compressedData == nil {
  54. autoreleasepool {
  55. if targetSizeInBytes > 0 {
  56. if dataSize <= targetSizeInBytes {
  57. compressedData = imageData as Data
  58. } else {
  59. let resizedImageData = NSMutableData()
  60. guard let resizedDestination = CGImageDestinationCreateWithData(resizedImageData as CFMutableData, kUTTypeJPEG, 1, nil) else {
  61. return
  62. }
  63. let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
  64. let contextBounds = CGRect(origin: .zero, size: newSize)
  65. if let context = CGContext(data: nil, width: Int(newSize.width), height: Int(newSize.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo) {
  66. context.interpolationQuality = .high
  67. context.draw(cgImage, in: contextBounds)
  68. let resizedCGImage = context.makeImage()
  69. if let resizedCGImage = resizedCGImage {
  70. CGImageDestinationAddImage(resizedDestination, resizedCGImage, optionsLow as CFDictionary)
  71. CGImageDestinationFinalize(resizedDestination)
  72. imageData = resizedImageData
  73. dataSize = CFDataGetLength(imageData)
  74. } else {
  75. compressedData = imageData as Data
  76. }
  77. }
  78. }
  79. } else {
  80. compressedData = imageData as Data
  81. }
  82. }
  83. }
  84. return compressedData
  85. }
  86. static func resizeImage(_ image: NSImage, toSize newSize: NSSize) -> Data? {
  87. let resizedImage = NSImage(size: newSize)
  88. resizedImage.lockFocus()
  89. NSGraphicsContext.current?.imageInterpolation = .high
  90. image.draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: NSRect.zero, operation: .copy, fraction: 1.0)
  91. resizedImage.unlockFocus()
  92. guard let imageData = resizedImage.tiffRepresentation,
  93. let bitmapImage = NSBitmapImageRep(data: imageData),
  94. let compressedData = bitmapImage.representation(using: .png, properties: [:]) else {
  95. return nil
  96. }
  97. return compressedData
  98. }
  99. static func needCompressImageLosslessly(image: NSImage, targetSize: CGSize, maxSizeInBytes: Int, targetCompression: CGFloat) -> String {
  100. let kFilePath: NSString = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.path + "KMImageOptimizationCache" as NSString
  101. let string: NSString = (kFilePath as String) + "/" + "optimization.png" as NSString
  102. if (!FileManager.default.fileExists(atPath: string.deletingLastPathComponent as String)) {
  103. try?FileManager.default.createDirectory(atPath: string.deletingLastPathComponent as String, withIntermediateDirectories: true, attributes: nil)
  104. }
  105. if (!FileManager.default.fileExists(atPath: string as String)) {
  106. FileManager.default.createFile(atPath: string as String, contents: nil)
  107. }
  108. if let compressedData = KMImageOptimization.compressImageLosslessly(image: image, targetSize: targetSize, maxSizeInBytes: maxSizeInBytes, targetCompression: targetCompression) {
  109. let saveURL = URL(fileURLWithPath: string as String)//URL(fileURLWithPath: "/path/to/save/compressed_image.png")
  110. do {
  111. try compressedData.write(to: saveURL)
  112. KMPrint("保存图像成功:\(string)")
  113. } catch {
  114. KMPrint("保存图像失败:\(error)")
  115. }
  116. }
  117. return string as String
  118. }
  119. }