iOS 超厉害的图片压缩技术(不失真)

转自:https://www.jianshu.com/p/76f7eb00ef13

原文:http://superdanny.link/2016/01/28/iOS-Upload-Image/

#pragma mark - 图片压缩

- (NSData *)resetSizeOfImageData:(UIImage *)sourceImage maxSize:(NSInteger)maxSize {

    //先判断当前质量是否满足要求,不满足再进行压缩

    __block NSData *finallImageData = UIImageJPEGRepresentation(sourceImage,1.0);

    NSUInteger sizeOrigin  = finallImageData.length;

    NSUInteger sizeOriginKB = sizeOrigin /1000;


    if(sizeOriginKB <= maxSize) {

        returnfinallImageData;

    }


    //获取原图片宽高比

    CGFloat sourceImageAspectRatio = sourceImage.size.width/sourceImage.size.height;

    //先调整分辨率

    CGSize defaultSize = CGSizeMake(1024,1024/sourceImageAspectRatio);

    UIImage *newImage = [selfnewSizeImage:defaultSize image:sourceImage];


    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 = [selfhalfFuntion:compressionQualityArr image:newImage sourceData:finallImageData maxSize:maxSize];

    //如果还是未能压缩到指定大小,则进行降分辨率

    while(finallImageData.length ==0) {

        //每次降100分辨率

        CGFloat reduceWidth =100.0;

        CGFloat reduceHeight =100.0/sourceImageAspectRatio;

        if(defaultSize.width-reduceWidth <=0|| defaultSize.height-reduceHeight <=0) {

            break;

        }

        defaultSize = CGSizeMake(defaultSize.width-reduceWidth, defaultSize.height-reduceHeight);

        UIImage *image = [selfnewSizeImage:defaultSize

            image:[UIImage imageWithData:UIImageJPEGRepresentation(newImage,[[compressionQualityArr lastObject] floatValue])]];

        finallImageData = [selfhalfFuntion:compressionQualityArr image:image sourceData:UIImageJPEGRepresentation(image,1.0) maxSize:maxSize];

    }

    returnfinallImageData;

}

#pragma mark 调整图片分辨率/尺寸(等比例缩放)

- (UIImage *)newSizeImage:(CGSize)size image:(UIImage *)sourceImage {

    CGSize newSize = CGSizeMake(sourceImage.size.width, sourceImage.size.height);


    CGFloat tempHeight = newSize.height / size.height;

    CGFloat tempWidth = newSize.width / size.width;


    if(tempWidth >1.0&& tempWidth > tempHeight) {

        newSize = CGSizeMake(sourceImage.size.width / tempWidth, sourceImage.size.height / tempWidth);

    }elseif(tempHeight >1.0&& tempWidth < tempHeight) {

        newSize = CGSizeMake(sourceImage.size.width / tempHeight, sourceImage.size.height / tempHeight);

    }


    UIGraphicsBeginImageContext(newSize);

    [sourceImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    returnnewImage;

}

#pragma mark 二分法

- (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", (unsigned long)sizeOriginKB);

        NSLog(@"\nstart:%zd\nend:%zd\nindex:%zd\n压缩系数:%lf", start, end, (unsigned long)index, [arr[index] floatValue]);


        if(sizeOriginKB > maxSize) {

            start = index +1;

        }elseif(sizeOriginKB < maxSize) {

            if(maxSize-sizeOriginKB < difference) {

                difference = maxSize-sizeOriginKB;

                tempData = finallImageData;

            }

            if(index<=0) {

                break;

            }

            end = index -1;

        }else{

            break;

        }

    }

    returntempData;

}


需求

很多时候我们上传图片经常遇到一些问题,要不就是图片质量变差,要不就是图片太大等等问题。这里,我找到了一个算是目前比较符合需求的解决方案。在原有基础上增加了动态压缩系数,改写成Swift版本,最底下贴出OC版本。

实现思路

先调整分辨率,分辨率可以自己设定一个值,大于的就缩小到这分辨率,小余的就保持原本分辨率。然后再根据图片最终大小来设置压缩比,比如传入maxSize = 30KB,最终计算大概这个大小的压缩比。基本上最终出来的图片数据根据当前分辨率能保持差不多的大小同时不至于太模糊,跟微信,微博最终效果应该是差不多的,代码仍然有待优化!

实现代码

Swift3.0之前旧版本压缩模式(建议不用,性能太差)

// MARK: - 降低质量

funcresetSizeOfImageData(source_image: UIImage, maxSize: Int)->NSData{

//先调整分辨率

varnewSize =CGSize(width: source_image.size.width, height: source_image.size.height)


lettempHeight = newSize.height /1024

lettempWidth  = newSize.width /1024


iftempWidth >1.0&& tempWidth > tempHeight {

newSize =CGSize(width: source_image.size.width / tempWidth, height: source_image.size.height / tempWidth)

        }

elseiftempHeight >1.0&& tempWidth < tempHeight {

newSize =CGSize(width: source_image.size.width / tempHeight, height: source_image.size.height / tempHeight)

        }


UIGraphicsBeginImageContext(newSize)

source_image.drawAsPatternInRect(CGRect(x:0, y:0, width: newSize.width, height: newSize.height))

letnewImage =UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()


//先判断当前质量是否满足要求,不满足再进行压缩

varfinallImageData =UIImageJPEGRepresentation(newImage,1.0)

letsizeOrigin      =Int64((finallImageData?.length)!)

letsizeOriginKB    =Int(sizeOrigin /1024)

ifsizeOriginKB <= maxSize {

returnfinallImageData!

        }


//保存压缩系数

letcompressionQualityArr =NSMutableArray()

letavg =CGFloat(1.0/250)

varvalue = avg


forvari =250; i>=1; i-- {

value =CGFloat(i)*avg

            compressionQualityArr.addObject(value)

        }


//调整大小

//说明:压缩系数数组compressionQualityArr是从大到小存储。

//思路:折半计算,如果中间压缩系数仍然降不到目标值maxSize,则从后半部分开始寻找压缩系数;反之从前半部分寻找压缩系数

finallImageData =UIImageJPEGRepresentation(newImage,CGFloat(compressionQualityArr[125]as!NSNumber))

ifInt(Int64((UIImageJPEGRepresentation(newImage,CGFloat(compressionQualityArr[125]as!NSNumber))?.length)!)/1024) > maxSize {

//拿到最初的大小

finallImageData =UIImageJPEGRepresentation(newImage,1.0)

//从后半部分开始

foridxin126..<250{

letvalue = compressionQualityArr[idx]

letsizeOrigin  =Int64((finallImageData?.length)!)

letsizeOriginKB =Int(sizeOrigin /1024)

print("当前降到的质量:\(sizeOriginKB)")

ifsizeOriginKB > maxSize {

print("\(idx)----\(value)")

finallImageData =UIImageJPEGRepresentation(newImage,CGFloat(valueas!NSNumber))

}else{

break

                }

            }

}else{

//拿到最初的大小

finallImageData =UIImageJPEGRepresentation(newImage,1.0)

//从前半部分开始

foridxin0..<125{

letvalue = compressionQualityArr[idx]

letsizeOrigin  =Int64((finallImageData?.length)!)

letsizeOriginKB =Int(sizeOrigin /1024)

print("当前降到的质量:\(sizeOriginKB)")

ifsizeOriginKB > maxSize {

print("\(idx)----\(value)")

finallImageData =UIImageJPEGRepresentation(newImage,CGFloat(valueas!NSNumber))

}else{

break

                }

            }

        }

returnfinallImageData!

    }

Swift3.0版本二分法压缩模式(推荐)


// MARK: - 降低质量

funcresetSizeOfImageData(sourceImage: UIImage!, maxSize: Int)->NSData{


//先判断当前质量是否满足要求,不满足再进行压缩

varfinallImageData =UIImageJPEGRepresentation(sourceImage,1.0)

letsizeOrigin      = finallImageData?.count

letsizeOriginKB    = sizeOrigin! /1024

ifsizeOriginKB <= maxSize {

returnfinallImageData!asNSData

        }


//获取原图片宽高比

letsourceImageAspectRatio = sourceImage.size.width/sourceImage.size.height

//先调整分辨率

vardefaultSize =CGSize(width:1024, height:1024/sourceImageAspectRatio)

letnewImage =self.newSizeImage(size: defaultSize, sourceImage: sourceImage)


finallImageData =UIImageJPEGRepresentation(newImage,1.0);


//保存压缩系数

letcompressionQualityArr =NSMutableArray()

letavg =CGFloat(1.0/250)

varvalue = avg


vari =250

repeat{

i -=1

value =CGFloat(i)*avg

            compressionQualityArr.add(value)

}whilei >=1


/*

        调整大小

        说明:压缩系数数组compressionQualityArr是从大到小存储。

        */

//思路:使用二分法搜索

finallImageData =self.halfFuntion(arr: compressionQualityArr.copy()as! [CGFloat], image: newImage, sourceData: finallImageData!, maxSize: maxSize)

//如果还是未能压缩到指定大小,则进行降分辨率

whilefinallImageData?.count==0{

//每次降100分辨率

letreduceWidth =100.0

letreduceHeight =100.0/sourceImageAspectRatio

if(defaultSize.width-CGFloat(reduceWidth)) <=0|| (defaultSize.height-CGFloat(reduceHeight)) <=0{

break

            }

defaultSize =CGSize(width: (defaultSize.width-CGFloat(reduceWidth)), height: (defaultSize.height-CGFloat(reduceHeight)))

letimage =self.newSizeImage(size: defaultSize, sourceImage:UIImage.init(data:UIImageJPEGRepresentation(newImage, compressionQualityArr.lastObjectas!CGFloat)!)!)

finallImageData =self.halfFuntion(arr: compressionQualityArr.copy()as! [CGFloat], image: image, sourceData:UIImageJPEGRepresentation(image,1.0)!, maxSize: maxSize)

        }


returnfinallImageData!asNSData

    }


// MARK: - 调整图片分辨率/尺寸(等比例缩放)

funcnewSizeImage(size: CGSize, sourceImage: UIImage)->UIImage{

varnewSize =CGSize(width: sourceImage.size.width, height: sourceImage.size.height)

lettempHeight = newSize.height / size.height

lettempWidth = newSize.width / size.width


iftempWidth >1.0&& tempWidth > tempHeight {

newSize =CGSize(width: sourceImage.size.width / tempWidth, height: sourceImage.size.height / tempWidth)

}elseiftempHeight >1.0&& tempWidth < tempHeight {

newSize =CGSize(width: sourceImage.size.width / tempHeight, height: sourceImage.size.height / tempHeight)

        }


UIGraphicsBeginImageContext(newSize)

sourceImage.draw(in:CGRect(x:0, y:0, width: newSize.width, height: newSize.height))

letnewImage =UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

returnnewImage!

    }


// MARK: - 二分法

funchalfFuntion(arr: [CGFloat], image: UIImage, sourceData finallImageData: Data, maxSize: Int)->Data? {

vartempFinallImageData = finallImageData


vartempData =Data.init()

varstart =0

varend = arr.count-1

varindex =0


vardifference =Int.max

whilestart <= end {

index = start + (end - start)/2


tempFinallImageData =UIImageJPEGRepresentation(image, arr[index])!


letsizeOrigin = tempFinallImageData.count

letsizeOriginKB = sizeOrigin /1024


print("当前降到的质量:\(sizeOriginKB)\n\(index)----\(arr[index])")


ifsizeOriginKB > maxSize {

start = index +1

}elseifsizeOriginKB < maxSize {

ifmaxSize-sizeOriginKB < difference {

                    difference = maxSize-sizeOriginKB

                    tempData = tempFinallImageData

                }

ifindex<=0{

break

                }

end = index -1

}else{

break

            }

        }

returntempData

    }

补充 OC 版本(推荐)

基于网友的要求,我把 OC 版本的代码也贴出来。


- (NSData*)resetSizeOfImageData:(UIImage*)source_image maxSize:(NSInteger)maxSize {

//先判断当前质量是否满足要求,不满足再进行压缩

__blockNSData*finallImageData =UIImageJPEGRepresentation(sourceImage,1.0);

NSUIntegersizeOrigin  = finallImageData.length;

NSUIntegersizeOriginKB = sizeOrigin /1000;


if(sizeOriginKB <= maxSize) {

returnfinallImageData;

    }


//获取原图片宽高比

CGFloatsourceImageAspectRatio = sourceImage.size.width/sourceImage.size.height;

//先调整分辨率

CGSizedefaultSize =CGSizeMake(1024,1024/sourceImageAspectRatio);

UIImage*newImage = [selfnewSizeImage:defaultSize image:sourceImage];


finallImageData =UIImageJPEGRepresentation(newImage,1.0);


//保存压缩系数

NSMutableArray*compressionQualityArr = [NSMutableArrayarray];

CGFloatavg  =1.0/250;

CGFloatvalue = avg;

for(inti =250; i >=1; i--) {

        value = i*avg;

        [compressionQualityArr addObject:@(value)];

    }


/*

    调整大小

    说明:压缩系数数组compressionQualityArr是从大到小存储。

    */

//思路:使用二分法搜索

finallImageData = [selfhalfFuntion:compressionQualityArr image:newImage sourceData:finallImageData maxSize:maxSize];

//如果还是未能压缩到指定大小,则进行降分辨率

while(finallImageData.length ==0) {

//每次降100分辨率

CGFloatreduceWidth =100.0;

CGFloatreduceHeight =100.0/sourceImageAspectRatio;

if(defaultSize.width-reduceWidth <=0|| defaultSize.height-reduceHeight <=0) {

break;

        }

defaultSize =CGSizeMake(defaultSize.width-reduceWidth, defaultSize.height-reduceHeight);

UIImage*image = [selfnewSizeImage:defaultSize

image:[UIImageimageWithData:UIImageJPEGRepresentation(newImage,[[compressionQualityArr lastObject] floatValue])]];

finallImageData = [selfhalfFuntion:compressionQualityArr image:image sourceData:UIImageJPEGRepresentation(image,1.0) maxSize:maxSize];

    }

returnfinallImageData;

}

#pragma mark 调整图片分辨率/尺寸(等比例缩放)

- (UIImage*)newSizeImage:(CGSize)size image:(UIImage*)sourceImage {

CGSizenewSize =CGSizeMake(sourceImage.size.width, sourceImage.size.height);


CGFloattempHeight = newSize.height / size.height;

CGFloattempWidth = newSize.width / size.width;


if(tempWidth >1.0&& tempWidth > tempHeight) {

newSize =CGSizeMake(sourceImage.size.width / tempWidth, sourceImage.size.height / tempWidth);

}elseif(tempHeight >1.0&& tempWidth < tempHeight) {

newSize =CGSizeMake(sourceImage.size.width / tempHeight, sourceImage.size.height / tempHeight);

    }


UIGraphicsBeginImageContext(newSize);

[sourceImage drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];

UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

returnnewImage;

}

#pragma mark 二分法

- (NSData*)halfFuntion:(NSArray*)arr image:(UIImage*)image sourceData:(NSData*)finallImageData maxSize:(NSInteger)maxSize {

NSData*tempData = [NSDatadata];

NSUIntegerstart =0;

NSUIntegerend = arr.count -1;

NSUIntegerindex =0;


NSUIntegerdifference =NSIntegerMax;

while(start <= end) {

index = start + (end - start)/2;


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


NSUIntegersizeOrigin = finallImageData.length;

NSUIntegersizeOriginKB = sizeOrigin /1024;

NSLog(@"当前降到的质量:%ld", (unsignedlong)sizeOriginKB);

NSLog(@"\nstart:%zd\nend:%zd\nindex:%zd\n压缩系数:%lf", start, end, (unsignedlong)index, [arr[index] floatValue]);


if(sizeOriginKB > maxSize) {

start = index +1;

}elseif(sizeOriginKB < maxSize) {

if(maxSize-sizeOriginKB < difference) {

                difference = maxSize-sizeOriginKB;

                tempData = finallImageData;

            }

if(index<=0) {

break;

            }

end = index -1;

}else{

break;

        }

    }

returntempData;

}

【更新日志】

2017年10月09日:修复了网友提出的二分法存在index为0时闪退问题。

2018年05月25日:将原本默认设置图片尺寸为1024*1024改成等比放大,同时降低分辨力也改成等比降低。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351