123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- //
- // 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
- }
- }
|