// // TSImageCompress.swift // Pods // // Created by 100Years on 2025/4/15. // import UIKit // MARK: - UIImage 扩展(Alpha 通道检测和缩放) public extension UIImage { /// 检查图片是否有 Alpha 通道(透明度) func hasAlphaChannel() -> Bool { guard let cgImage = self.cgImage else { return false } let alphaInfo = cgImage.alphaInfo return alphaInfo == .first || alphaInfo == .last || alphaInfo == .premultipliedFirst || alphaInfo == .premultipliedLast } /// 按比例缩放图片 func resized(withScaleFactor scaleFactor: CGFloat) -> UIImage { let newSize = CGSize(width: size.width * scaleFactor, height: size.height * scaleFactor) let renderer = UIGraphicsImageRenderer(size: newSize) return renderer.image { _ in self.draw(in: CGRect(origin: .zero, size: newSize)) } } } public class TSImageCompress { // MARK: - 核心压缩方法 public static func compressImageToTargetSize( _ image: UIImage, targetSizeKB: Int, preserveTransparency: Bool ) -> Data? { let targetBytes = targetSizeKB * 1024 let hasAlpha = image.hasAlphaChannel() let usePNG = preserveTransparency && hasAlpha if usePNG { let compressPNGImage = compressPNGImage(image, targetBytes: targetBytes) if let compressedData = compressPNGImage { // saveData(compressedData: compressedData) print("PNG 压缩后大小: \(compressedData.count / 1024)KB") } return compressPNGImage } else { let compressJPEGImage = compressJPEGImage(image, targetBytes: targetBytes) if let compressJPEGImage = compressJPEGImage { // saveData(compressedData: compressJPEGImage) print("JPEG 压缩后大小: \(compressJPEGImage.count / 1024)KB") } return compressJPEGImage } } // MARK: - PNG 压缩逻辑(调整尺寸) static private func compressPNGImage(_ image: UIImage, targetBytes: Int) -> Data? { var currentImage = image var currentData = currentImage.pngData() var currentBytes = currentData?.count ?? 0 // 如果已经满足目标大小,直接返回 guard currentBytes > targetBytes else { return currentData } var scaleFactor: CGFloat = 0.9 let minScaleFactor: CGFloat = 0.1 while scaleFactor >= minScaleFactor { let scaledImage = currentImage.resized(withScaleFactor: scaleFactor) guard let newData = scaledImage.pngData() else { break } let newBytes = newData.count if newBytes <= targetBytes { return newData // 达到目标 } if newBytes >= currentBytes { break // 无法继续优化 } currentImage = scaledImage currentData = newData currentBytes = newBytes scaleFactor -= 0.1 } return currentBytes <= targetBytes ? currentData : nil } // MARK: - JPEG 压缩逻辑(先调质量,后调尺寸) static private func compressJPEGImage(_ image: UIImage, targetBytes: Int) -> Data? { var compressionQuality: CGFloat = 1.0 var currentImage = image var currentData = currentImage.jpegData(compressionQuality: compressionQuality) var currentBytes = currentData?.count ?? 0 // 第一步:降低 JPEG 质量 while currentBytes > targetBytes && compressionQuality > 0.1 { compressionQuality -= 0.1 currentData = currentImage.jpegData(compressionQuality: compressionQuality) currentBytes = currentData?.count ?? 0 } guard currentBytes > targetBytes else { return currentData } // 第二步:缩小尺寸 var scaleFactor: CGFloat = 0.9 let minScaleFactor: CGFloat = 0.1 while scaleFactor >= minScaleFactor { let scaledImage = currentImage.resized(withScaleFactor: scaleFactor) guard let newData = scaledImage.jpegData(compressionQuality: compressionQuality) else { break } let newBytes = newData.count if newBytes <= targetBytes { return newData // 达到目标 } if newBytes >= currentBytes { break // 无法继续优化 } currentImage = scaledImage currentData = newData currentBytes = newBytes scaleFactor -= 0.1 } return currentBytes <= targetBytes ? currentData : nil } } extension TSImageCompress { static func saveData(compressedData:Data){ // 写入到沙盒的 "Images" 子目录 let fileName = "compressed_\(Date().timeIntervalSince1970).jpg" if let fileURL = writeDataToSandbox(data: compressedData, fileName: fileName, subDirectory: "Images") { print("文件已保存至: \(fileURL.path)") // 可选:读取文件验证 if let savedData = try? Data(contentsOf: fileURL) { print("读取文件大小: \(savedData.count / 1024)KB") } } } static func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] // 返回第一个路径(通常唯一) } static func writeDataToSandbox(data: Data, fileName: String, subDirectory: String? = nil) -> URL? { let baseDirectory: URL // 1. 确定基础目录(默认 Documents) baseDirectory = getDocumentsDirectory() // 2. 拼接子目录(如果存在) var targetDirectory = baseDirectory if let subDir = subDirectory { targetDirectory = baseDirectory.appendingPathComponent(subDir) } // 3. 创建子目录(如果不存在) do { try FileManager.default.createDirectory(at: targetDirectory, withIntermediateDirectories: true, attributes: nil) } catch { print("创建目录失败: \(error.localizedDescription)") return nil } // 4. 拼接最终文件路径 let fileURL = targetDirectory.appendingPathComponent(fileName) // 5. 写入数据 do { try data.write(to: fileURL) // print("文件写入成功: \(fileURL.path)") return fileURL } catch { print("写入文件失败: \(error.localizedDescription)") return nil } } }