iOS 之图像压缩策略

图像压缩是一种非常常见的场景。

而图像的压缩大小已经压缩质量直接会影响到上传的速度,严重影响了用户的体验度。

举个例子:100条评论数据,从头到尾,每一个评论中都包含,用户头像,评论的图片,那么如果这个不做任何处理,用户上传的图像可能是100kb,也有可能是1Mb大小的高清图像,甚至可能更大。在加载次界面的时候就会出现长时间的空白视图,虽然有 SDWebImageYYWebImage 等优秀第三方框架实现了异步加载,不会造成界面卡顿,然而,极差的用户体验这肯定是不可避免的。

谈及图像压缩,不得不说一下,微信在图像压缩方面是做的是真的很不错,无论是在压缩大小或者是压缩保真方面都处理得很完美。

最近在做压缩图像的时候整理出了三种不同压缩策略,各自有各自的优缺点,可以根据项目不同的场景调用不同的方法。

仿照微信压缩的思路进行压缩

图像缩放的比列为:

1
2
3
4
5
6
> 宽高均 <= 1280,图片尺寸大小保持不变
宽或高 > 1280 && 宽高比 <= 2,取较大值等于1280,较小值等比例压缩
宽或高 > 1280 && 宽高比 > 2 && 宽或高 < 1280,图片尺寸大小保持不变
宽高均 > 1280 && 宽高比 > 2,取较小值等于1280,较大值等比例压缩
优点:压缩速度相对较快,清晰度基本可以达到要求
缺点:可能达不到指定压缩大小
1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface WYImageScaleTool : NSObject

+ (NSData*)compressOfImageWithWeiChat:(UIImage*)source_image
maxSize:(NSInteger)maxSize;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#import "WYImageScaleTool.h"
@implementation WYImageScaleTool

+ (instancetype)sharedManager {
static WYImageScaleTool *_instace =nil;
static dispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
_instace = [[WYImageScaleTool alloc] init];
});
return _instace;
}

+ (NSData*)compressOfImageWithWeiChat:(UIImage*)source_image
maxSize:(NSInteger)maxSize {
CGFloat compression = 1.0f;
CGFloat minCompression = 0.5f;
NSData *imageData = UIImageJPEGRepresentation(source_image, compression);
CGFloat imageFileSize = imageData.length/1024;
NSLog(@"原图大小 = %lu Kb", (unsignedlong)imageData.length/1024);

WYImageScaleTool *tool = [WYImageScaleTool sharedManager];
UIImage *newImage = [tool Compresimage:source_image];

// 每次减少的比例
float scale =0.05;
// 循环条件:没到最小压缩比例,且没压缩到目标大小
while((compression > minCompression) &&
(imageFileSize > maxSize))
{
compression -= scale;
imageData =UIImageJPEGRepresentation(newImage,
compression);

// NSLog(@"\n压缩比 -> %g, \n压缩后的大小 -> %lu Kb, \n缩放后的大小 -> %lu Kb, \n原图大小 -> %lu Kb", compression, (unsigned long)imageData.length/1024, (unsigned long)scaleData.length/1024, (unsigned long)originalData.length/1024);
}

NSLog(@"压缩后图片大小 = %lu Kb", (unsigned long)imageData.length/1024);
return imageData;
}

- (UIImage*)Compresimage:(UIImage*)image {
CGSize size = [self CompressSizeImage:image];
UIImage *reImage = [self resizedImage:sizeimage:image];
return reImage;
}

- (CGSize)CompressSizeImage:(UIImage*)image {
CGFloat width = image.size.width;
CGFloat height = image.size.height;
CGFloat boundary =1280;
if(width < boundary && height < boundary) {
return CGSizeMake(width, height);
}

CGFloat ratio =MAX(width, height) /MIN(width, height);
if(ratio <=2) {
CGFloat x =MAX(width, height) / boundary;
if(width > height) {
width = boundary;
height = height / x;
}else{
height = boundary;
width = width / x;
}

}else{

if(MIN(width, height) >= boundary) {
CGFloat x =MIN(width, height) / boundary;
if(width < height) {
width = boundary;
height = height / x;
}else{
height = boundary;
width = width / x;
}
}

}
return CGSizeMake(width, height);
}

- (UIImage*)resizedImage:(CGSize)newSize image:(UIImage*)image {
CGRect newRect =CGRectMake(0,0, newSize.width, newSize.height);
UIGraphicsBeginImageContext(newRect.size);
UIImage *newImage = [[UIImage alloc] initWithCGImage:image.CGImagescale:1 orientation:image.imageOrientation];
[newImage drawInRect:newRect];
newImage =UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

@end

图像进行等比例压缩

进行等比例压缩

1
2
3
> 图像宽度参照 1242px 进行等比例缩
优点:图像不会变形,压缩时间适中
缺点:可能达不到指定压缩大小
1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface WYImageScaleTool : NSObject

+ (NSData *)compressOfImageWithEqualProportion:(UIImage *)source_image
maxSize:(NSInteger)maxSize;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#import "WYImageScaleTool.h"

@implementation WYImageScaleTool

+ (instancetype)sharedManager {
static WYImageScaleTool *_instace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[WYImageScaleTool alloc] init];
});
return _instace;
}

+ (NSData *)compressOfImageWithEqualProportion:(UIImage *)source_image maxSize:(NSInteger)maxSize {
CGFloat compression = 1.0f;
CGFloat minCompression = 0.5f;
NSData * imageData = UIImageJPEGRepresentation(source_image, compression);
NSLog(@"原图大小 = %lu Kb", (unsigned long)imageData.length/1024);

CGFloat imageFileSize = imageData.length / 1024;

WYImageScaleTool *tool = [WYImageScaleTool sharedManager];
UIImage *newImage = [tool scaleToWidth:1242 scaleImage:source_image];

// 每次减少的比例
float scale = 0.05;
// 循环条件:没到最小压缩比例,且没压缩到目标大小
while ((compression > minCompression) &&
(imageFileSize > maxSize))
{
compression -= scale;
imageData = UIImageJPEGRepresentation(newImage,
compression);

// NSLog(@"\n压缩比 -> %g, \n压缩后的大小 -> %lu Kb, \n缩放后的大小 -> %lu Kb, \n原图大小 -> %lu Kb", compression, (unsigned long)imageData.length/1024, (unsigned long)scaleData.length/1024, (unsigned long)originalData.length/1024);

}

NSLog(@" 压缩后图片大小 = %lu Kb", (unsigned long)imageData.length/1024);
return imageData;
}

/** 在原有基础上图像等比例缩放 */
- (UIImage *)scaleToWidth:(CGFloat)width scaleImage:(UIImage *)image {
if (width > image.size.width) {
return image;
}
CGFloat height = (width / image.size.width) * image.size.height;
CGRect rect = CGRectMake(0, 0, width, height);
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

二分法压缩图像

1
2
3
> 先判断当前质量是否满足要求,不满足再进行压缩图像
优点:能压缩到指定大小
缺点:压缩相对较慢,图像清晰度可能达不到,不建议高清图片用此方法
1
2
3
4
5
6
7
8
9
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface WYImageScaleTool : NSObject

+ (NSData *)compressOfImageWithTwoPoints:(UIImage *)source_image
maxSize:(NSInteger)maxSize;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#import "WYImageScaleTool.h"

@implementation WYImageScaleTool

+ (instancetype)sharedManager {
static WYImageScaleTool *_instace = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[WYImageScaleTool alloc] init];
});
return _instace;
}

+ (NSData *)compressOfImageWithTwoPoints:(UIImage *)source_image maxSize:(NSInteger)maxSize
{
__block NSData *finallImageData = UIImageJPEGRepresentation(source_image,1.0);
NSUInteger sizeOrigin = finallImageData.length;
NSUInteger sizeOriginKB = sizeOrigin / 1024;

if (sizeOriginKB <= maxSize) {
return finallImageData;
}

WYImageScaleTool *tool = [WYImageScaleTool sharedManager];
NSLog(@"原图大小 = %lu Kb", (unsigned long)finallImageData.length/1024);

// 先调整分辨率
CGSize defaultSize = CGSizeMake(1024, 1024);
UIImage *newImage = [tool newSizeImage:defaultSize image:source_image];
finallImageData = UIImageJPEGRepresentation(newImage,1.0);

// 保存压缩系数
NSMutableArray *compressionQualityArr = [NSMutableArray array];
CGFloat avg = 1.0/250;
CGFloat value = avg;
for (int i = 250; i >= 1; i--) {
value = i*avg;
[compressionQualityArr addObject:@(value)];
}

/* 调整大小 压缩系数数组compressionQualityArr是从大到小存储。 */
/** 使用二分法搜索 */
finallImageData = [tool halfFuntion:compressionQualityArr image:newImage sourceData:finallImageData maxSize:maxSize];

//如果还是未能压缩到指定大小,则进行降分辨率
while (finallImageData.length == 0) {
//每次降100分辨率
if (defaultSize.width-100 <= 0 || defaultSize.height-100 <= 0) {
break;
}
defaultSize = CGSizeMake(defaultSize.width-100, defaultSize.height-100);
UIImage *image = [tool newSizeImage:defaultSize
image:[UIImage imageWithData:UIImageJPEGRepresentation(newImage,[[compressionQualityArr lastObject] floatValue])]];
finallImageData = [tool halfFuntion:compressionQualityArr image:image sourceData:UIImageJPEGRepresentation(image,1.0) maxSize:maxSize];
}

NSLog(@" 压缩后图片大小 = %lu Kb", (unsigned long)finallImageData.length/1024);
return finallImageData;
}

/** 调整图片分辨率/尺寸(等比例缩放) */
- (UIImage *)newSizeImage:(CGSize)size image:(UIImage *)source_image {
CGSize newSize = CGSizeMake(source_image.size.width, source_image.size.height);

CGFloat tempHeight = newSize.height / size.height;
CGFloat tempWidth = newSize.width / size.width;

if (tempWidth > 1.0 && tempWidth > tempHeight) {
newSize = CGSizeMake(source_image.size.width / tempWidth, source_image.size.height / tempWidth);
}
else if (tempHeight > 1.0 && tempWidth < tempHeight){
newSize = CGSizeMake(source_image.size.width / tempHeight, source_image.size.height / tempHeight);
}

UIGraphicsBeginImageContext(newSize);
[source_image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

/** 二分法压缩 */
- (NSData *)halfFuntion:(NSArray *)arr image:(UIImage *)image sourceData:(NSData *)finallImageData maxSize:(NSInteger)maxSize {
NSData *tempData = [NSData data];
NSUInteger start = 0;
NSUInteger end = arr.count - 1;
NSUInteger index = 0;

NSUInteger difference = NSIntegerMax;
while(start <= end) {
index = start + (end - start)/2;

finallImageData = UIImageJPEGRepresentation(image,[arr[index] floatValue]);

NSUInteger sizeOrigin = finallImageData.length;
NSUInteger sizeOriginKB = sizeOrigin / 1024;

// NSLog(@"当前降到的质量 -> %ld 压缩系数 -> %lg", (unsigned long)sizeOriginKB, [arr[index] floatValue]);

if (sizeOriginKB > maxSize) {
start = index + 1;
} else if (sizeOriginKB < maxSize) {
if (maxSize-sizeOriginKB < difference) {
difference = maxSize-sizeOriginKB;
tempData = finallImageData;
}
if (index<=0) {
break;
}
end = index - 1;
} else {
break;
}
}
return tempData;
}

@end