|
@@ -0,0 +1,131 @@
|
|
|
+//
|
|
|
+// KMImageOptimization.swift
|
|
|
+// PDF Master
|
|
|
+//
|
|
|
+// Created by lizhe on 2023/5/25.
|
|
|
+//
|
|
|
+
|
|
|
+import AppKit
|
|
|
+import CoreGraphics
|
|
|
+import CoreImage
|
|
|
+
|
|
|
+class KMImageOptimization: NSObject {
|
|
|
+ static func compressImageLosslessly(image: NSImage, targetSize: CGSize, maxSizeInBytes: Int, targetCompression: CGFloat) -> Data? {
|
|
|
+ guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ //宽高自适应
|
|
|
+ let newSize: CGSize
|
|
|
+ let widthRatio = targetSize.width / image.size.width
|
|
|
+ let heightRatio = targetSize.height / image.size.height
|
|
|
+
|
|
|
+ if widthRatio > heightRatio {
|
|
|
+ newSize = CGSize(width: targetSize.height * image.size.width / image.size.height, height: targetSize.height)
|
|
|
+ } else {
|
|
|
+ newSize = CGSize(width: targetSize.width, height: targetSize.width * image.size.height / image.size.width)
|
|
|
+ }
|
|
|
+
|
|
|
+ let options: [CFString: Any] = [
|
|
|
+ kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height),
|
|
|
+ kCGImageSourceCreateThumbnailFromImageAlways: true,
|
|
|
+ kCGImageDestinationLossyCompressionQuality: 1.0
|
|
|
+ ]
|
|
|
+
|
|
|
+ guard var imageData = CFDataCreateMutable(nil, 0) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let destination = CGImageDestinationCreateWithData(imageData, kUTTypeJPEG, 1, nil) else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)
|
|
|
+ CGImageDestinationFinalize(destination)
|
|
|
+
|
|
|
+ var dataSize = CFDataGetLength(imageData)
|
|
|
+ let targetSizeInBytes = Int(CGFloat(maxSizeInBytes) * targetCompression)
|
|
|
+
|
|
|
+ if dataSize <= targetSizeInBytes {
|
|
|
+ return imageData as Data
|
|
|
+ }
|
|
|
+
|
|
|
+ var compressedData: Data?
|
|
|
+
|
|
|
+ let optionsLow: [CFString: Any] = [
|
|
|
+ kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height) / 2,
|
|
|
+ kCGImageSourceCreateThumbnailFromImageAlways: true,
|
|
|
+ kCGImageDestinationLossyCompressionQuality: targetCompression
|
|
|
+ ]
|
|
|
+
|
|
|
+ let optionsHigh: [CFString: Any] = [
|
|
|
+ kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height),
|
|
|
+ kCGImageSourceCreateThumbnailFromImageAlways: true,
|
|
|
+ kCGImageDestinationLossyCompressionQuality: targetCompression
|
|
|
+ ]
|
|
|
+
|
|
|
+ while compressedData == nil {
|
|
|
+ autoreleasepool {
|
|
|
+ if targetSizeInBytes > 0 {
|
|
|
+ if dataSize <= targetSizeInBytes {
|
|
|
+ compressedData = imageData as Data
|
|
|
+ } else {
|
|
|
+ let resizedImageData = NSMutableData()
|
|
|
+
|
|
|
+ guard let resizedDestination = CGImageDestinationCreateWithData(resizedImageData as CFMutableData, kUTTypeJPEG, 1, nil) else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
|
|
|
+ let contextBounds = CGRect(origin: .zero, size: newSize)
|
|
|
+
|
|
|
+ if let context = CGContext(data: nil, width: Int(newSize.width), height: Int(newSize.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo) {
|
|
|
+ context.interpolationQuality = .high
|
|
|
+ context.draw(cgImage, in: contextBounds)
|
|
|
+ let resizedCGImage = context.makeImage()
|
|
|
+
|
|
|
+ if let resizedCGImage = resizedCGImage {
|
|
|
+ CGImageDestinationAddImage(resizedDestination, resizedCGImage, optionsLow as CFDictionary)
|
|
|
+ CGImageDestinationFinalize(resizedDestination)
|
|
|
+
|
|
|
+ imageData = resizedImageData
|
|
|
+ dataSize = CFDataGetLength(imageData)
|
|
|
+ } else {
|
|
|
+ compressedData = imageData as Data
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ compressedData = imageData as Data
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return compressedData
|
|
|
+ }
|
|
|
+
|
|
|
+ static func needCompressImageLosslessly(image: NSImage, targetSize: CGSize, maxSizeInBytes: Int, targetCompression: CGFloat) -> String {
|
|
|
+ let kFilePath: NSString = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.path + "KMImageOptimizationCache" as NSString
|
|
|
+ let string: NSString = (kFilePath as String) + "/" + "optimization.png" as NSString
|
|
|
+ if (!FileManager.default.fileExists(atPath: string.deletingLastPathComponent as String)) {
|
|
|
+ try?FileManager.default.createDirectory(atPath: string.deletingLastPathComponent as String, withIntermediateDirectories: true, attributes: nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!FileManager.default.fileExists(atPath: string as String)) {
|
|
|
+ FileManager.default.createFile(atPath: string as String, contents: nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 示例用法
|
|
|
+ if let compressedData = KMImageOptimization.compressImageLosslessly(image: image, targetSize: targetSize, maxSizeInBytes: maxSizeInBytes, targetCompression: targetCompression) {
|
|
|
+ let saveURL = URL(fileURLWithPath: string as String)//URL(fileURLWithPath: "/path/to/save/compressed_image.png")
|
|
|
+ do {
|
|
|
+ try compressedData.write(to: saveURL)
|
|
|
+ // 图像已成功保存在指定路径中
|
|
|
+ print("保存图像成功:\(string)")
|
|
|
+ } catch {
|
|
|
+ print("保存图像失败:\(error)")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return string as String
|
|
|
+ }
|
|
|
+}
|