// // KMImageOptimization.swift // PDF Reader Pro // // 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 resizeImage(_ image: NSImage, toSize newSize: NSSize) -> Data? { let resizedImage = NSImage(size: newSize) resizedImage.lockFocus() NSGraphicsContext.current?.imageInterpolation = .high image.draw(in: NSRect(x: 0, y: 0, width: newSize.width, height: newSize.height), from: NSRect.zero, operation: .copy, fraction: 1.0) resizedImage.unlockFocus() guard let imageData = resizedImage.tiffRepresentation, let bitmapImage = NSBitmapImageRep(data: imageData), let compressedData = bitmapImage.representation(using: .png, properties: [:]) else { return nil } 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) KMPrint("保存图像成功:\(string)") } catch { KMPrint("保存图像失败:\(error)") } } return string as String } }