欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS学习——图片压缩到指定大小以内

程序员文章站 2022-05-29 12:57:18
一、图片压缩简述 在我们开发过程中,有可能会遇到拍照、或者从相册中选择图片,要么单选或者多选,然后上传图片到服务器,一般情况下一张图片可能3-4M,如果类似微信朋友圈上传9张图片大约是 35M左右,如果我们上传 35M左右的图片到服务器,可想而知后台的压力有多大,最主要的还是特别耗时,如果是在网速比 ......

一、图片压缩简述

  在我们开发过程中,有可能会遇到拍照、或者从相册中选择图片,要么单选或者多选,然后上传图片到服务器,一般情况下一张图片可能3-4m,如果类似微信朋友圈上传9张图片大约是 35m左右,如果我们上传 35m左右的图片到服务器,可想而知后台的压力有多大,最主要的还是特别耗时,如果是在网速比较慢,那么用户上传图片可能需要4-5分钟,那么用户就会受不了,可能会退出应用。所有在开发过程中,考虑到手机性能、网络性能等因素的影响,更重要的是后台服务器的内存、网络等性能的限制,我们再通过网络发送图片等信息时不能发送超过一定大小的图片,如果超过了指定大小,我们需要进行压缩后发送。

  首先,我们必须明确图片的压缩其实是两个概念:

  • ” 是指文件体积变小,但是像素数不变,长宽尺寸不变,那么质量可能下降。
  • ” 是指文件的尺寸变小,也就是像素数减少,而长宽尺寸变小,文件体积同样会减小。

二、图片压缩的实现

 2.1 “压”处理

  对于“压”的功能,我们一般是使用系统提供的uiimagejpegrepresentation或uiimagepngrepresentation方法实现,如:

// return image as png. may return nil if image has no cgimageref or invalid bitmap format
uikit_extern nsdata *uiimagepngrepresentation(uiimage *image); 

 // return image as jpeg. may return nil if image has no cgimageref or invalid bitmap format. compression is 0(most)..1(least)                              
uikit_extern nsdata *uiimagejpegrepresentation(uiimage *image, cgfloat compressionquality); 

//uiimagejpegrepresentation需要传两个参数,
//第一个参数是图片对象
//第二个参数是压的系数,其值范围为0~1
nsdata *imgdata=uiimagejpegrepresentation(image, 0.5);

//uiimagepngrepresentation只需要传一个参数,就是图片对象
nsdata *imgdata = uiimagepngrepresentation(image);

  uiimagepngrepresentation要比uiimagejpegrepresentation(uiimage* image, 1.0)返回的图片数据量大很多。同样的一张照片, 使用uiimagepngrepresentation(image)返回的数据量大小为199k,而uiimagejpegrepresentation(image, 1.0)返回的数据量大小只为140k,比前者少了59k。

  如果对图片的清晰度要求不是极高,建议使用uiimagejpegrepresentation,可以大幅度降低图片数据量.其中uiimagejpegrepresentation(uiimage *image, cgfloat compressionquality)提供了一个压缩比率的参数compressionquality,但是实际体验确实compressionquality并不能够按照设定好的数值,比例压缩。比如一张2.9m的图片(jpg格式),通过uiimagejpegrepresentation方法设置不同压缩比进行压缩后的大小如下:

2019-03-13 13:54:33.546342+0800 cjmobile[52591:15764262] compression = 1.000000 image length = 7076.682617 kb
2019-03-13 13:54:33.658606+0800 cjmobile[52591:15764262] compression = 0.500000 image length = 1490.095703 kb
2019-03-13 13:54:33.748077+0800 cjmobile[52591:15764262] compression = 0.250000 image length = 671.213867 kb
2019-03-13 13:54:33.834126+0800 cjmobile[52591:15764262] compression = 0.125000 image length = 550.979492 kb
2019-03-13 13:54:33.918830+0800 cjmobile[52591:15764262] compression = 0.062500 image length = 532.168945 kb
2019-03-13 13:54:34.004086+0800 cjmobile[52591:15764262] compression = 0.031250 image length = 532.107422 kb
2019-03-13 13:54:34.089819+0800 cjmobile[52591:15764262] compression = 0.015625 image length = 532.107422 kb

  通过上面的结果我们可以看到,compressionquality压缩系数跟最后文件的大小并没有明显的关系,不同的图片呈现不同结果,而且最后压缩比减小但是得到的图片大小没有变化。本人对图片存储格式不是很了解,所以对出现这样的情况不是很了解,如果有对此比较了解的同学烦请赐教。但是图片颜色细节越单一,图片可压缩的比率会越高。

  uiimagepngrepresentation虽然可以让我们控制压缩质量比例,但是我们看到这个压缩比compressionquality实际上很难确定一张图片是否能压缩到误差范围内,无法实现精确压缩

2.2 “缩”处理

  uiimagepngrepresentation虽然可以让我们控制压缩质量比例,但是我们看到这个压缩比compressionquality实际上很难确定一张图片是否能压缩到误差范围内,无法实现精确压缩。所以我们对图片只“压”而不缩,有时候是达不到我们的需求的。因此,必要的时候,我们需要适当地对图片“缩”一“缩“尺寸,就可以满足我们的需求。

通过 [sourceimage drawinrect:cgrectmake(0, 0, targetwidth, targetheight)] 可以进行图片“缩”的功能。示例如下:

- (uiimage*)compressimage:(uiimage*)sourceimage totargetwidth:(cgfloat)targetwidth {
    //获取原图片的大小尺寸
    cgsize imagesize = sourceimage.size;
    cgfloat width = imagesize.width;
    cgfloat height = imagesize.height;
    //根据目标图片的宽度计算目标图片的高度
    cgfloat targetheight = (targetwidth / width) * height;
    //开启图片上下文
    uigraphicsbeginimagecontext(cgsizemake(targetwidth, targetheight));
    //绘制图片
    [sourceimage drawinrect:cgrectmake(0,0, targetwidth, targetheight)];
    //从上下文中获取绘制好的图片
    uiimage*newimage = uigraphicsgetimagefromcurrentimagecontext();
    //关闭图片上下文
    uigraphicsendimagecontext();
    
    return newimage;
}

   通过“缩”处理,我们可以将图片压缩到任何我们制定的大小尺寸内,但是这种处理,我们改变了原先图片的尺寸大小,无法保证图片的质量

三、图片压缩到指定大小以内实现

  当我们需要对图片的大小进行限制时,我们首先应该优先采取“压”处理,如果“压”处理达不到要求,那么我们在“压”处理的结果上继续进行“缩”处理,直到图片的大小达到我们的要求为止。

/*!
 *  @brief 使图片压缩后刚好小于指定大小
 *
 *  @param image 当前要压缩的图 maxlength 压缩后的大小
 *
 *  @return 图片对象
 */
//图片质量压缩到某一范围内,如果后面用到多,可以抽成分类或者工具类,这里压缩递减比二分的运行时间长,二分可以限制下限。
- (uiimage *)compressimagesize:(uiimage *)image tobyte:(nsuinteger)maxlength{
    //首先判断原图大小是否在要求内,如果满足要求则不进行压缩,over
    cgfloat compression = 1;
    nsdata *data = uiimagejpegrepresentation(image, compression);
    if (data.length < maxlength) return image;
    //原图大小超过范围,先进行“压处理”,这里 压缩比 采用二分法进行处理,6次二分后的最小压缩比是0.015625,已经够小了
    cgfloat max = 1;
    cgfloat min = 0;
    for (int i = 0; i < 6; ++i) {
        compression = (max + min) / 2;
        data = uiimagejpegrepresentation(image, compression);
        if (data.length < maxlength * 0.9) {
            min = compression;
        } else if (data.length > maxlength) {
            max = compression;
        } else {
            break;
        }
    }
    //判断“压处理”的结果是否符合要求,符合要求就over
    uiimage *resultimage = [uiimage imagewithdata:data];
    if (data.length < maxlength) return resultimage;
    
    //缩处理,直接用大小的比例作为缩处理的比例进行处理,因为有取整处理,所以一般是需要两次处理
    nsuinteger lastdatalength = 0;
    while (data.length > maxlength && data.length != lastdatalength) {
        lastdatalength = data.length;
        //获取处理后的尺寸
        cgfloat ratio = (cgfloat)maxlength / data.length;
        cgsize size = cgsizemake((nsuinteger)(resultimage.size.width * sqrtf(ratio)),
                                 (nsuinteger)(resultimage.size.height * sqrtf(ratio)));
        //通过图片上下文进行处理图片
        uigraphicsbeginimagecontext(size);
        [resultimage drawinrect:cgrectmake(0, 0, size.width, size.height)];
        resultimage = uigraphicsgetimagefromcurrentimagecontext();
        uigraphicsendimagecontext();
        //获取处理后图片的大小
        data = uiimagejpegrepresentation(resultimage, compression);
    }
    
    return resultimage;
}