// // UploadImageProcessors.swift // suixinkan // import Foundation import UIKit import UniformTypeIdentifiers /// 头像图片处理器,负责压缩和规范化用户选择的头像图片。 enum AvatarImageProcessor { static let maxPixelLength: CGFloat = 1024 static let jpegCompressionQuality: CGFloat = 0.82 /// 处理后的上传图片实体,包含数据、文件名和内容类型。 struct ProcessedImage: Equatable { let data: Data let fileName: String let contentType: UTType } /// 将原始图片数据转换为适合头像上传的 JPEG 数据。 static func process(data: Data, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> ProcessedImage { try UploadImageRenderer.process( data: data, fileNamePrefix: "avatar", maxPixelLength: maxPixelLength, jpegCompressionQuality: jpegCompressionQuality, timestamp: timestamp ) } } /// 实名证件图片处理器,负责压缩和规范化身份证正反面图片。 enum RealNameImageProcessor { static let maxPixelLength: CGFloat = 1800 static let jpegCompressionQuality: CGFloat = 0.88 /// 将原始图片数据转换为适合实名认证上传的 JPEG 数据。 static func process(data: Data, side: RealNameImageSide, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> AvatarImageProcessor.ProcessedImage { try UploadImageRenderer.process( data: data, fileNamePrefix: "real_name_\(side.rawValue)", maxPixelLength: maxPixelLength, jpegCompressionQuality: jpegCompressionQuality, timestamp: timestamp ) } } /// 银行卡图片处理器,负责压缩银行卡正反面图片。 enum BankCardImageProcessor { static let maxPixelLength: CGFloat = 1800 static let jpegCompressionQuality: CGFloat = 0.88 static func process(data: Data, side: BankCardImageSide, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> AvatarImageProcessor.ProcessedImage { try UploadImageRenderer.process( data: data, fileNamePrefix: "bank_card_\(side.rawValue)", maxPixelLength: maxPixelLength, jpegCompressionQuality: jpegCompressionQuality, timestamp: timestamp ) } } /// 实名认证证件图片面向实体,区分人像面和国徽面。 enum RealNameImageSide: String { case front case back } /// 银行卡图片面向实体。 enum BankCardImageSide: String { case front case back } /// 上传图片处理错误实体,表示图片无效或编码失败。 enum UploadImageProcessingError: LocalizedError, Equatable { case invalidImage case encodingFailed var errorDescription: String? { switch self { case .invalidImage: "请选择有效的图片" case .encodingFailed: "图片处理失败,请重新选择" } } } /// 上传图片渲染器,提供通用图片缩放、铺底和 JPEG 编码能力。 private enum UploadImageRenderer { static func process( data: Data, fileNamePrefix: String, maxPixelLength: CGFloat, jpegCompressionQuality: CGFloat, timestamp: TimeInterval ) throws -> AvatarImageProcessor.ProcessedImage { guard let image = UIImage(data: data), image.size.width > 0, image.size.height > 0 else { throw UploadImageProcessingError.invalidImage } let normalized = image.normalizedForUpload(maxPixelLength: maxPixelLength) guard let jpegData = normalized.jpegData(compressionQuality: jpegCompressionQuality), !jpegData.isEmpty else { throw UploadImageProcessingError.encodingFailed } return AvatarImageProcessor.ProcessedImage( data: jpegData, fileName: "\(fileNamePrefix)_\(Int(timestamp)).jpg", contentType: .jpeg ) } } private extension UIImage { func normalizedForUpload(maxPixelLength: CGFloat) -> UIImage { let longestSide = max(size.width, size.height) let scale = min(1, maxPixelLength / longestSide) let targetSize = CGSize( width: max(1, (size.width * scale).rounded()), height: max(1, (size.height * scale).rounded()) ) let format = UIGraphicsImageRendererFormat.default() format.scale = 1 format.opaque = true let renderer = UIGraphicsImageRenderer(size: targetSize, format: format) return renderer.image { context in UIColor.white.setFill() context.fill(CGRect(origin: .zero, size: targetSize)) draw(in: CGRect(origin: .zero, size: targetSize)) } } }