一、概述
“压” 是指文件体积变小,但是像素数不变,长宽尺寸不变,那么质量可能下降。(图片压完之后,占用内存的bitMap不变)
“缩” 是指文件的尺寸变小,也就是像素数减少,而长宽尺寸变小,文件体积同样会减小。
二、“压”
1、图片质量压缩原理
采用一些算法,将几个像素点用一个像素点的数据表示
2、图片压完之后,占用内存的bitMap不变论证:
UIImage *image = [UIImage imageNamed:@"WechatIMG435.jpeg"];
NSData *imgData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WechatIMG435" ofType:@"jpeg"]];
NSLog(@"imgData:%ld",imgData.length);
;
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
NSLog(@"rawData.length:%ld",(long)CFDataGetLength(rawData));
NSLog(@"----------------------我是分割线---------------------");
NSData *compressData = UIImageJPEGRepresentation(image, 0.1);
UIImage *compressImg = [UIImage imageWithData:compressData];
NSString *newPath = [NSString stringWithFormat:@"%@/%@",[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject],@"compressImg.jpeg"];
BOOL a = [compressData writeToFile:newPath atomically:YES];
NSLog(@"imgData:%ld",compressData.length);
CFDataRef compressRawData = CGDataProviderCopyData(CGImageGetDataProvider(compressImg.CGImage));
NSLog(@"rawData.length:%ld",(long)CFDataGetLength(compressRawData));
打印结果:
2019-07-09 18:01:38.172504+0800 Img[47267:15114761] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
2019-07-09 18:01:38.263858+0800 Img[47267:15114761] [framework] CUIThemeStore: No theme registered with id=0
2019-07-09 18:01:38.330603+0800 Img[47267:15114761] imgData:10983
2019-07-09 18:01:38.331847+0800 Img[47267:15114761] rawData.length:480000
2019-07-09 18:01:38.331955+0800 Img[47267:15114761] ----------------------我是分割线---------------------
2019-07-09 18:01:38.334699+0800 Img[47267:15114761] imgData:5560
2019-07-09 18:01:38.335806+0800 Img[47267:15114761] rawData.length:480000
如上,图片"压"后,占用内存的bitmap没有改变
三、“缩”
1、原理
图片尺寸压缩,减小体积,图片bitmap减小
2、方案
使用ImageIO接口,避免在改变图片大小的过程中产生临时的bitmap,就能够在很大程度上减少内存的占有从而避免由此导致的app闪退问题。
UIImage *image = [UIImage imageNamed:@"WechatIMG435.jpeg"];
NSData *imgData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WechatIMG435" ofType:@"jpeg"]];
NSLog(@"imgData:%ld",imgData.length);
;
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
NSLog(@"rawData.length:%ld",(long)CFDataGetLength(rawData));
NSLog(@"----------------------我是分割线---------------------");
UIImage *sizeCompressImg = [self thumbnailForData:imgData maxPixelSize:image.size.width * 0.25];
CFDataRef sizeCompressRawData = CGDataProviderCopyData(CGImageGetDataProvider(sizeCompressImg.CGImage));
NSLog(@"rawData.length:%ld",(long)CFDataGetLength(sizeCompressRawData));
打印结果
2019-07-09 20:10:04.300806+0800 Img[48279:15418157] libMobileGestalt MobileGestalt.c:890: MGIsDeviceOneOfType is not supported on this platform.
2019-07-09 20:10:04.378632+0800 Img[48279:15418157] [framework] CUIThemeStore: No theme registered with id=0
2019-07-09 20:10:04.448211+0800 Img[48279:15418157] imgData:10983
2019-07-09 20:10:04.449632+0800 Img[48279:15418157] rawData.length:480000
2019-07-09 20:10:04.449805+0800 Img[48279:15418157] ----------------------我是分割线---------------------
2019-07-09 20:10:04.450898+0800 Img[48279:15418157] rawData.length:30000
分析:图片bitmap占用从 480000 减小到 30000减小了16倍
image.png
四、图片质量压缩方案探索
1、分次循环压缩
/// 对图片进行质量压缩
///
/// - Parameter targetByte: 压缩目标字节数
/// - Returns: 压缩之后的data 对象
func compressByQulity(toTargetByte targetByte:NSInteger)->NSData?{
let bigImage = self
let imgData = bigImage.dataFromImage()
guard imgData != nil else{
return nil
}
var targetImageData:Data = imgData!
print("lalala compress before:%d",targetImageData.count)
//分次循环压缩
//当前imgData 大小
var targetImageDataLength:Int = targetImageData.count
//上次压缩imgData 大小
var lastTargetImageDataLength : Int = 0
//png jpeg应机器学习同学要求,同时采用jpeg压缩格式
while(targetImageDataLength > targetByte ){
//上次压缩图片大小赋值
lastTargetImageDataLength = targetImageDataLength;
//压缩
let img = UIImage.init(data: targetImageData)
guard img != nil else{
break
}
let compressImgData = img!.jpegData(compressionQuality: CGFloat(UIImage.qualityPerCompressRate))
guard compressImgData != nil else{
break
}
targetImageData = compressImgData!
targetImageDataLength = targetImageData.count
guard targetImageDataLength < lastTargetImageDataLength else {
break
}
}
print("lalala compress after:%d",targetImageData.count)
return targetImageData as NSData
}
lalala =-------我是分割线-----------------
lalala compress before:%d 1811323
lalala compress after:%d 543473
lalala compress after:%d 544901
lalala =-------我是分割线-----------------
lalala compress before:%d 1559291
lalala compress after:%d 526630
lalala compress after:%d 527984
lalala =-------我是分割线-----------------
lalala compress before:%d 2522415
lalala compress after:%d 766597
lalala compress after:%d 767281
lalala =-------我是分割线-----------------
lalala compress before:%d 2287580
lalala compress after:%d 753524
lalala compress after:%d 754047
以上算法存在以下问题
(1)、第二次一定比第一次大,所以只能跑一遍
(2)、UIImage 与 data互转 来回互转 data数据会改变即 UIImageJPEGRepresentation 1.0 从本地读取到data数据也会改变,图片的质量也会受到影响
UIImage *image = [UIImage imageNamed:@"WechatIMG435.jpeg"];
NSData *imgData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WechatIMG435" ofType:@"jpeg"]];
NSLog(@"imgData:%ld",imgData.length);
NSData *jpegData = UIImageJPEGRepresentation(image, 1.0);
NSLog(@"jpegData:%ld",jpegData.length);
UIImage *newImage = [UIImage imageWithData:jpegData];
NSData *newData = UIImageJPEGRepresentation(newImage, 1.0);
NSLog(@"newData:%ld",newData.length);
打印结果:
2019-07-09 20:56:55.355619+0800 Img[48690:15554037] imgData:10983
2019-07-09 20:56:55.359325+0800 Img[48690:15554037] jpegData:47681
2019-07-09 20:56:55.363122+0800 Img[48690:15554037] newData:51046
2、 二分法寻找最佳压缩率
(1)UIImageJPEGRepresentation(image, scale) API调研
a、图片压缩测试 原图大小:7801942
scale | 实际 | 正常比例计算 |
---|---|---|
0.1 | 114823 | 780192 |
0.2 | 127503 | 1560384 |
0.3 | 156226 | 2340577 |
0.4 | 204067 | |
0.5 | 261299 | |
0.6 | 331804 | |
0.7 | 438994 | |
0.8 | 508924 | |
0.9 | 640913 |
从上图得出 实际压缩率不是传入的值,实际压缩率一定大于压缩结果
b、压缩极限测试
原图大小:193737
压缩比率
0.01 113710
对上面压缩之后的值再次压缩
0.01 113710
压缩到极限值之后,压缩之后的数据,将不在压缩
(2)、压缩目标
压缩 目标 物理大小 500k,容错范围 5000.8 ~~5001.2
(3)、UIImageJPEGRepresentation能力之外数据处理方案
a、图片压缩0.01之后还是不能到500k,直接返回压缩最小值
b、循环到第6次数,压缩率为0.984,已经压缩为最大值,还没有进入理想数据范围,将不在进行压缩,直接返回UIImageJPEGRepresentation 1.0压缩数值
c、本来就是有损压缩,无论png还是jpeg 统一使用jpeg压缩方案与UIImage->data转化方法
func compressByQulity(toTargetByte targetByte:NSInteger)->NSData?{
let bigImage = self
//本来就是有损压缩,无论png还是jpeg 统一使用jpeg 注意:如果为png格式,转化之后filesize会比源文件小很多,这里将进行一次有损压缩
let imgData = bigImage.jpegData(compressionQuality: 1.0)
guard imgData != nil else{
return nil
}
var targetImageData:Data = imgData!
print("lalala =-------我是分割线-----------------")
print("lalala compress before---",targetImageData.count)
guard targetImageData.count > targetByte else {
print("lalala compress after:%d",targetImageData.count)
return targetImageData as NSData
}
//分次循环寻找compressRate大小
//png jpeg应机器学习同学要求,同时采用jpeg压缩格式
var compressionRate:CGFloat = 1.0
var max:CGFloat = 1
var min:CGFloat = 0
//指数二分处理,s首先计算最小值
compressionRate = CGFloat(pow(2.0, -6.0))
let smallestImgData = bigImage.jpegData(compressionQuality: compressionRate)
guard smallestImgData != nil else {
return nil
}
if smallestImgData!.count < targetByte{
for _ in 0..<6 {
compressionRate = (max + min)/2.0
let compressData = bigImage.jpegData(compressionQuality: compressionRate)
guard compressData != nil else {
break
}
//容错区间范围0.8~1.2 409600 614400
if compressData!.count < Int(Double(targetByte) * 0.8){
min = compressionRate
}else if compressData!.count > Int(Double(targetByte) * 1.2){
max = compressionRate
}else{
//找到压缩最佳值
targetImageData = compressData!
print("lalala ---0.9:%d~~~1.1:%d",Int(Double(targetByte) * 0.8),Int(Double(targetByte) * 1.2))
break
}
print("lalala ---\(compressionRate)----compressDatalenth:%d",compressData?.count)
}
}
print("lalala compress after:%d",targetImageData.count)
return targetImageData as NSData
}
原因:image data 相互转换 data忽大忽小
结果:可能是300k,但是已经是jpeg能提供的最佳压缩率了
lalala compress before--- 1006180
lalala ---0.5----compressDatalenth:%d Optional(156856)
lalala ---0.75----compressDatalenth:%d Optional(266978)
lalala ---0.875----compressDatalenth:%d Optional(321495)
lalala ---0.9375----compressDatalenth:%d lalala ---0.96875----compressDatalenth:%d Optional(352076)
lalala ---0.984375----compressDatalenth:%d Optional(359699)
此方案基于 UIImageJPEGRepresentation 传入参数值 由小到大,压缩值 也 由小变大规律
规律论证如下:
参考链接:iOS图片压缩处理
为什么实际大小和占用空间不一样
iOS优秀的图片压缩处理方案(系统UIimagejpeg和uikit以及imageI/处理)
图像压缩原理
图片有损压缩原理
网友评论