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改成等比放大,同时降低分辨力也改成等比降低。

你可能感兴趣的:(iOS 超厉害的图片压缩技术(不失真))