图像的压缩算法--尺寸压缩、格式压缩和品质压缩

图像分辨率指图像中存储的信息量,是每英寸图像内有多少个像素点,分辨率的单位为PPI(Pixels Per Inch),通常叫做像素每英寸。图像分辨率一般被用于ps中,用来改变图像的清晰度。
一个影像可被定义是一个二维的函数 f(x,y),其中x和y是空间平面坐标,在任意一对坐标轴(x,y),f的大小称为这幅影像在该点的强度(instensity)或灰阶(gray level).也即,灰阶是指地物电磁波辐射强度表现在黑白影像上的色调深浅的等级,是划分地物波谱特征的尺度。
一张图片的图片大小主要取决于图片尺寸(点阵图如6000*4000就时指的图片尺寸),分辨率(每英寸图像内有多少个像素点),灰阶。图像压缩算法(学过图像处理的人知道,图片最简单的是位图,就时bitMap,对应的是原始无压缩的点阵图,而jpeg等图像算法能把相同的连续图像点按照对应图像压缩算法和分辨率对位图进行压缩保存,也可以还原位图。由于jpeg和png是有损压缩,压缩后和原图有失真)。所以图片压缩分三类:尺寸压缩最快成比例压缩(理论上可以压缩到接近无限小,你把他压缩到4个点了也没有什么物理意义了)、图像格式压缩(如:把bitMap转换为jpeg,PNG等图片格式)、图像品质压缩(UIImageJPEGRepresentation(image, compressionQuality)进行分辨率和压缩算法方面的压缩。注意:compressionQuality是图像质量参数和图像分辨率强相关不是简单压缩比,这种压缩有压缩下限。其中图像格式压缩和图像品质压缩可以同时进行,把png等非JPG格式的图片通过UIImageJPEGRepresentation函数及compressionQuality压缩参数进行压缩。由于图片的点阵图尺寸不会变,而连续相同的像素点也是固定,最多你把一个像素点灰阶从很大压缩到1,使用UIImageJPEGRepresentation压缩图像主要是图像格式方面的压缩,而修改它的compressionQuality主要是修改它的分辨率,所以通过UIImageJPEGRepresentation修改compressionQuality对图片压缩到一定程度,图像大小是无限趋近于不变化而不是无限接近0),也就是超大尺寸的图片经过非尺寸方面的压缩后可能很小,但是不会无限小通常有100k以上。
UIImageJPEGRepresentation(compressedImage, compressionQuality)其中的compressionQuality可以称为图片质量参数,它能决定压缩后的图片大小和分辨率。一般的图片,当compressionQuality为0.7时和原图片大小比较接近,但是这和图像的尺寸,色彩丰富度等各种图像指标有关,有的是compressionQuality为0.8,有的是compressionQuality为0.5时更接近压缩前的图片大小,这个没有固定的值,它取决为图片本身,不能用一张图片设置为0.5时解决原图片都认为compressionQuality为0.5接近原图片。以前我专门测试过compressionQuality的值对图片原来大小的比例。只要测试10兆超级大的尺寸图片,全黑图片,全白图片,色彩特别复杂的图片。有的文章一概而论的说法本来就是错误的。
注意把一般的jpg图片通过UIImagePNGRepresentation(compressedImage)转变为png格式的图片,大约会变大1.2倍左右(具体多少根据图片来决定,但是基本都变大是肯定的)。但是png支持透明背景图片,把png 格式透明背景的图片转变jpg格式的图片,图片背景会变成默认的黑色。
由于常见的jpeg和png都有损压缩,若非必要,尽量别来回压缩。图像压缩很费时间和内存的,特别是时间。
图像可以通过图像尺寸和图像大小(图像格式和分辨率方面的压缩)两方面的多次压缩。
下面是按照要求,把图像压缩到指定的大小或接近最小值的循环压缩图像的算法(注意这个很浪费时间,有大图像可能需要浪费你几秒很正长,大尺寸图像很可能压缩不到你设置的很小的值,但是能接近它的最小值。这种压缩的特点是图片大小压缩很快,并且不改变图片的尺寸):

static const long long shareImageMaxLength = 1024*1024;
-(UIImage *)getImageWithDada : (NSData *)data
{
    if(!data)
    {
        return nil;
    }
    UIImage *image = [UIImage imageWithData:data];
    if(data.length <= shareImageMaxLength)
    {
        NSLog(@"data.length:%lu", (unsigned long)data.length);
        return image;
    }
    else
    {
        CGFloat compressionQualityArr[1001] = {0};
        compressionQualityArr[0] = 0.0001;
        for(NSInteger i = 1; i <= 1000; i++)
        {
            compressionQualityArr[i] = i*0.001;
        }
        NSData *compressedData = [self findImageWithImage:image lowerBoundary:0 upperBoundary:1000 compressionQualityArr:compressionQualityArr];
        return [UIImage imageWithData:compressedData];
    }
}

-(NSData *)findImageWithImage : (UIImage *)image
                 lowerBoundary : (NSInteger)lowerBoundary
                 upperBoundary : (NSInteger)upperBoundary
         compressionQualityArr : (CGFloat *)compressionQualityArr

{
    NSInteger x = (lowerBoundary + upperBoundary) / 2;
    NSData *data = UIImageJPEGRepresentation(image, compressionQualityArr[x]);
    if(data.length <= shareImageMaxLength)
    {
        NSLog(@"data.length:%lu,compressionQualityArr[%ld]:%f", (unsigned long)data.length, (long)x, compressionQualityArr[x]);
        return data;
    }
    if ((data.length > shareImageMaxLength) && (x > 0))//说明在compressionQualityArr[lowerBoundary]-compressionQualityArr[x]范围参数之中
    {
        return [self findImageWithImage:image lowerBoundary:lowerBoundary upperBoundary:x compressionQualityArr:compressionQualityArr];
    }
    else
    {
        NSLog(@"data.length:%lu,compressionQualityArr[%ld]:%f", (unsigned long)data.length, (long)x, compressionQualityArr[x]);
        return data;
    }
}

下面是把宽或高大于指定尺寸的图片等比例压缩到指定宽高范围内的图片的算法。图像大小一般在苹果手机上影响下载的快慢等用户体验问题,但是图像尺寸太大,苹果电脑直接不支持。我们的app甚至出现过SDWebImage下载图片时崩溃,没有办法只有通过修改SDWebImage这个开源库,压缩图像的尺寸才避免了崩溃。前不久做多图片分享时,调用系统分享组件UIActivityViewController,结果直接分享的界面出不来。只要尺寸指定不超过iphone X,一个图片再怎么大也大不到哪里去,但是超大尺寸的图片搞个几百兆没有问题。下面图片宽高设置为不超过iphone X ps像素。由于我是通过NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]]直接得到的图像数据流,所以压缩函数的入参是(NSData *)data。

/**分享的图片最大宽度,单位:ps像素(不是物理像素)*/
static const long long maxShareImageWidth = 800;
/**分享的图片最大高度,单位:ps像素(不是物理像素)*/
static const long long maxShareImageHeight = 800;
-(UIImage *)compressImageWithDada : (NSData *)data
{
    UIImage *image = [UIImage imageWithData:data];
    if((image.size.height <= maxShareImageHeight) && (image.size.width <= maxShareImageWidth))
    {
        return image;
    }
    if((maxShareImageWidth == 0) || (maxShareImageHeight == 0))
    {
        //防止除数为0而crash,理论上不该出现maxIphoneScreenMaxSizeHeight为0
        return nil;
    }
//    float imageWidth = image.size.width;
//    float imageHeight = image.size.height;
    //800x800
    float width = maxShareImageWidth;
    float height = image.size.height/(image.size.width/width);
    
    if((image.size.width >= maxShareImageWidth) && (image.size.height >= maxShareImageHeight))
    {
        width = maxShareImageWidth;
        height = image.size.height/(image.size.width/width);
        if(height > maxShareImageHeight)
        {
            height = maxShareImageHeight;
            width = image.size.width/(image.size.height/height);
        }
    }
    else if(image.size.width >= maxShareImageWidth)
    {
        height = maxShareImageHeight;
        width = image.size.width/(image.size.height/height);
    }
//    else if(image.size.height >= maxIphoneScreenMaxSizeHeight)
//    {
//        width = maxIphoneScreenMaxSizeWidth;
//        height = image.size.height/(image.size.width/width);
//    }
    
    
//    float widthScale = imageWidth /width;
//    float heightScale = imageHeight /height;
    
    // 创建一个bitmap的context
    // 并把它设置成为当前正在使用的context
    UIGraphicsBeginImageContext(CGSizeMake(width, height));
    [image drawInRect:CGRectMake(0, 0, width , height)];
//    if (widthScale > heightScale) {
//        [image drawInRect:CGRectMake(0, 0, imageWidth /heightScale , height)];
//    }
//    else {
//        [image drawInRect:CGRectMake(0, 0, width , imageHeight /widthScale)];
//    }
    
    // 从当前context中创建一个改变大小后的图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    // 使当前的context出堆栈
    UIGraphicsEndImageContext();
    
    return newImage;
    
}

当然你不怕压缩太费时间你也可以把图像尺寸压缩和图像大小方面的压缩(我暂时也叫它图像分辨方面方面的压缩吧)。
下面是先用图像大小压缩,当压缩到最小还是太大就采用图像尺寸方面的压缩。这种压缩不能解决大尺寸图片苹果手机支持的问题。因为相对苹果支持的尺寸是大尺寸的图片经过图像大小方面的压缩可能已经达标了,但是图像尺寸其实没变仍旧超标。这个只是两种图像压缩都使用的例子。当然你可以设计成先图像尺寸递归压缩然后图像大小太大再使用图像大小压缩的方法,这种方法我就不写了,自己改造就可以了。

-(UIImage *)getImageWithDada : (NSData *)data
{
    if(!data)
    {
        return nil;
    }
    UIImage *image = [UIImage imageWithData:data];
    if(data.length <= shareImageMaxLength)
    {
        NSLog(@"data.length:%lu", (unsigned long)data.length);
        return image;
    }
    else
    {
        CGFloat compressionQualityArr[1001] = {0};
        compressionQualityArr[0] = 0.0001;
        for(NSInteger i = 1; i <= 1000; i++)
        {
            compressionQualityArr[i] = i*0.001;
        }
        NSData *compressedData = [self findImageWithImage:image lowerBoundary:0 upperBoundary:1000 compressionQualityArr:compressionQualityArr];
        if(data.length <= shareImageMaxLength)
        {
            return [UIImage imageWithData:compressedData];
        }
        else
        {
            return [self compressImageWithDada:compressedData];
        }
    }
}
    原文作者:郏国上
    原文地址: https://blog.csdn.net/jia12216/article/details/81334363
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞