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