一.压缩方式和方法
1.压缩方式
1.1 质量压缩
1.2 尺寸压缩
1.3 质量和尺寸共同压缩
2.压缩方法
2.1质量压缩
public func jpegData(compressionQuality: CGFloat) -> Data? // return image as JPEG. May return nil if image has no CGImageRef or invalid bitmap format. compression is 0(most)..1(least)
2.2尺寸压缩
var resultImage:UIImage? = nil
let size: CGSize = CGSize(width: width, height: height)
UIGraphicsBeginImageContext(size)
originalImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
if let image:UIImage = UIGraphicsGetImageFromCurrentImageContext(){
resultImage = image
}
UIGraphicsEndImageContext()
2.3 使用ImageIO进行尺寸压缩(待实践,貌似可以避免在生成图片过程中产生的bitmap,这样可以极大程度的减少内存和cpu的消耗)
二.压缩到指定大小
1.压缩质量(二分法)
/// 压缩图片
/// - Parameter maxLength: 最大字节数 如:需要500kb 传0.5
/// - Returns: 压缩后的图片data
func compressQualityWithMaxLength(maxLength: Float) -> Data? {
let maxValue:Float = maxLength * 1024 * 1024//最大字节数
var compressionQuality:CGFloat = 1
var compressData:Data? = self.jpegData(compressionQuality: compressionQuality)
var max:CGFloat = 1
var min:CGFloat = 0
if let data = compressData, Float(data.count) < maxValue{
return compressData//原图大小少于所需大小直接返回
}
for _ in 0 ... 6 {
compressionQuality = (max + min) * 0.5
if let data:Data = self.jpegData(compressionQuality: compressionQuality){
compressData = data
if Float(data.count) < maxValue * 0.9 {
min = compressionQuality // 缩小压缩比例
}else if Float(data.count) > maxValue {
max = compressionQuality // 扩大压缩比例
}else{
break
}
}
}
return compressData
}
- 缺点:图片的大小是由图片的宽高和像素决定的,而压质量其实只能决定部分图片大小。当图片的宽高过大时,是不能通过压质量来决定最优的图片大小
- 解决方案:质量和尺寸都压缩
1.压缩尺寸
/// 压缩图片
///
/// - Parameter maxLength: 最大字节数 如:需要500kb 传0.5
/// - Returns: 压缩后的图片data
func compressSizeWithMaxLength(maxLength: Float) -> Data? {
if let jpegData:Data = self.jpegData(compressionQuality: 1) {
var resultImage: UIImage = self
var resultData: NSData = NSData(data: jpegData)
let maxValue:Int = Int(maxLength * 1024 * 1024)//所需大小
var lastLength: Int = 0
if resultData.length <= maxValue {
return resultData as Data//原图片大小小于所需大小直接返回
}
//条件:生成的图片大小是否满足所需大小
while resultData.length > maxValue, resultData.length != lastLength{
lastLength = resultData.length
let route: CGFloat = CGFloat(maxValue) / CGFloat(lastLength)
let width: CGFloat = resultImage.size.width * CGFloat(sqrtf(Float(route)))
let height: CGFloat = resultImage.size.height * CGFloat(sqrtf(Float(route)))
let size: CGSize = CGSize(width: width, height: height)
//压缩尺寸并生成新的图片
UIGraphicsBeginImageContext(size)
resultImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
resultImage = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
UIGraphicsEndImageContext()
if let data = resultImage.jpegData(compressionQuality: 1) {
resultData = NSData(data: data)
}
}
return resultData as Data
}
return nil
}
- 缺点:用UIGraphicsBeginImageContext去绘画新的图片产生临时的bitmap占用内存,draw会消耗cpu,如果绘画的次数过多,内存会爆增、app卡顿,最后crash。通过所需大小和总大小的比例来计算尺寸缩放,可能会造成图片尺寸太小达不到尺寸上的需求。
有使用autoreleasepool来释放,但是效果并不理想,当压缩的图片一次性过多的时,可能autoreleasepool来不及释放- 解决方案:减少UIGraphicsBeginImageContext的绘画次数
3.质量尺寸结合压缩
/// 压缩图片
/// - Parameter maxLength: 最大字节数 如:需要500kb 传0.5
/// - Returns: 压缩后的图片data
func compressWithMaxLength(maxLength: Float) -> Data? {
let maxValue:Int = Int(maxLength * 1024 * 1024)//最大字节数
var compressionQuality:CGFloat = 1
var compressData:Data? = self.jpegData(compressionQuality: compressionQuality)
var max:CGFloat = 1
var min:CGFloat = 0
if let data = compressData, data.count < maxValue{
return compressData//原图大小少于所需大小直接返回
}
for _ in 0 ... 6 {
compressionQuality = (max + min) * 0.5
if let data:Data = self.jpegData(compressionQuality: compressionQuality){
compressData = data
if Float(data.count) < Float(maxValue) * 0.9 {
min = compressionQuality // 缩小压缩比例
}else if data.count > maxValue {
max = compressionQuality // 扩大压缩比例
}else{
break
}
}
}
if let data = compressData, data.count > maxValue{//压缩质量后大小还是不符合再进行尺寸压缩
var resultImage: UIImage = self
var resultData: NSData = NSData(data: data)
var lastLength: Int = 0
//条件:生成的图片大小是否满足所需大小
while resultData.length > maxValue, resultData.length != lastLength{
lastLength = resultData.length
let route: CGFloat = CGFloat(maxValue) / CGFloat(lastLength)
let width: CGFloat = resultImage.size.width * CGFloat(sqrtf(Float(route)))
let height: CGFloat = resultImage.size.height * CGFloat(sqrtf(Float(route)))
let size: CGSize = CGSize(width: width, height: height)
//压缩尺寸并生成新的图片
UIGraphicsBeginImageContext(size)
resultImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
resultImage = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage.init()
UIGraphicsEndImageContext()
if let data = resultImage.jpegData(compressionQuality: 1) {
resultData = NSData(data: data)
}
}
return resultData as Data
}else{
//压缩质量后大小符合 直接返回
return compressData
}
}
普通场景下用这个方法应该足够了,如果一次性压4到8张大图,还是会造成crash,crash的原因就是因为UIGraphicsBeginImageContext的绘画次数太多内存爆增导致,当然这个也看手机,好一点的手机可能不会。
4.仿微信压缩图片
/// 仿微信压缩图片
/// - Return: 压缩后的图片data
func smartCompress() -> Data? {
/** 仿微信算法 **/
var tempImage = self
let width:Int = Int(self.size.width)
let height:Int = Int(self.size.height)
var updateWidth = width
var updateHeight = height
let longSide = max(width, height)
let shortSide = min(width, height)
let scale:CGFloat = CGFloat(CGFloat(shortSide) / CGFloat(longSide))
// 大小压缩
if shortSide < 1080 || longSide < 1080 { // 如果宽高任何一边都小于 1080
updateWidth = width
updateHeight = height
} else { // 如果宽高都大于 1080
if width < height { // 说明短边是宽
updateWidth = 1080
updateHeight = Int(1080 / scale)
} else { // 说明短边是高
updateWidth = Int(1080 / scale)
updateHeight = 1080
}
}
let size: CGSize = CGSize(width: updateWidth, height: updateHeight)
UIGraphicsBeginImageContext(size)
tempImage.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: size))
if let image:UIImage = UIGraphicsGetImageFromCurrentImageContext(){
tempImage = image
}
UIGraphicsEndImageContext()
let resultData:Data? = tempImage.jpegData(compressionQuality:0.5)//质量压缩一半
return resultData
}
该方法是先压尺寸一次,再压质量一次,减少UIGraphicsBeginImageContext绘画次数为一次,避免了多操作,从而减少了内存的占用,cpu的使用率,而且图片清晰,避免了crash。