目前在iOS上对于图片的优化点有很多,例如图片解码、图片渐加载和图片尺寸处理。这篇文章是说明目前iOS 代码中修改图片尺寸的两种方法,以及这两种方法区别和注意点。

修改图片尺寸的两种方法

1. 画布ImageContext(UIKit)

/** 利用画布对图片尺寸进行修改

@param data ---- 图片Data

@param maxPixelSize ---- 图片最大宽/高尺寸 ,设置后图片会根据最大宽/高 来等比例缩放图片

@return 目标尺寸的图片Image */

+ (UIImage*) getThumImgOfConextWithData:(NSData*)data withMaxPixelSize:(int)maxPixelSize

{

UIImage *imgResult = nil;

if(data == nil) { return imgResult; }

if(data.length <= 0) { return imgResult; }

if(maxPixelSize <= 0) { return imgResult; }

const int sizeTo = maxPixelSize; // 图片最大的宽/高

CGSize sizeResult;

UIImage *img = [UIImage imageWithData:data];

if(img.size.width > img.size.height){ // 根据最大的宽/高 值,等比例计算出最终目标尺寸

float value = img.size.width/ sizeTo;

int height = img.size.height / value;

sizeResult = CGSizeMake(sizeTo, height);

} else {

float value = img.size.height/ sizeTo;

int width = img.size.width / value;

sizeResult = CGSizeMake(width, sizeTo);

}

UIGraphicsBeginImageContextWithOptions(sizeResult, NO, 0);

[img drawInRect:CGRectMake(0, 0, sizeResult.width, sizeResult.height)];

img = nil;

imgResult = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return imgResult;

}

2. image I/O 创建省略图

/** Image I/O 获取指定尺寸的图片,返回的结果Image 目标尺寸大小 <= 图片原始尺寸大小

@param data ---- 图片Data

@param maxPixelSize ---- 图片最大宽/高尺寸 ,设置后图片会根据最大宽/高 来等比例缩放图片

@return 目标尺寸的图片Image */

+ (UIImage*) getThumImgOfImgIOWithData:(NSData*)data withMaxPixelSize:(int)maxPixelSize

{

UIImage *imgResult = nil;

if(data == nil) { return imgResult; }

if(data.length <= 0) { return imgResult; }

if(maxPixelSize <= 0) { return imgResult; }

const float scale = [UIScreen mainScreen].scale;

const int sizeTo = maxPixelSize * scale;

CFDataRef dataRef = (__bridge CFDataRef)data;

/* CGImageSource的键值说明

kCGImageSourceCreateThumbnailWithTransform - 设置缩略图是否进行Transfrom变换

kCGImageSourceCreateThumbnailFromImageAlways - 设置是否创建缩略图,无论原图像有没有包含缩略图,默认kCFBooleanFalse,影响 CGImageSourceCreateThumbnailAtIndex 方法

kCGImageSourceCreateThumbnailFromImageIfAbsent - 设置是否创建缩略图,如果原图像有没有包含缩略图,则创建缩略图,默认kCFBooleanFalse,影响 CGImageSourceCreateThumbnailAtIndex 方法

kCGImageSourceThumbnailMaxPixelSize - 设置缩略图的最大宽/高尺寸 需要设置为CFNumber值,设置后图片会根据最大宽/高 来等比例缩放图片

kCGImageSourceShouldCache - 设置是否以解码的方式读取图片数据 默认为kCFBooleanTrue,如果设置为true,在读取数据时就进行解码 如果为false 则在渲染时才进行解码 */

CFDictionaryRef dicOptionsRef = (__bridge CFDictionaryRef) @{

(id)kCGImageSourceCreateThumbnailFromImageIfAbsent : @(YES),

(id)kCGImageSourceThumbnailMaxPixelSize : @(sizeTo),

(id)kCGImageSourceShouldCache : @(YES),

};

CGImageSourceRef src = CGImageSourceCreateWithData(dataRef, nil);

CGImageRef thumImg = CGImageSourceCreateThumbnailAtIndex(src, 0, dicOptionsRef); //注意:如果设置 kCGImageSourceCreateThumbnailFromImageIfAbsent为 NO,那么 CGImageSourceCreateThumbnailAtIndex 会返回nil

CFRelease(src); // 注意释放对象,否则会产生内存泄露

imgResult = [UIImage imageWithCGImage:thumImg scale:scale orientation:UIImageOrientationUp];

if(thumImg != nil){

CFRelease(thumImg); // 注意释放对象,否则会产生内存泄露

}

return imgResult;

}

需要注意的是, 使用Image I/O 时,设置kCGImageSourceThumbnailMaxPixelSize 的最大高/宽值时,如果设置值超过了图片文件原本的高/宽值,那么CGImageSourceCreateThumbnailAtIndex获取的图片尺寸将是原始图片文件的尺寸。比如,设置 kCGImageSourceThumbnailMaxPixelSize 为600,而如果图片文件尺寸为580*212,那么最终获取到的图片尺寸是580 * 212。

小注释:UIKit处理很大的图片时,容易出现内存崩溃(超过App可使用内存的上限),原因是[UIImage drawInRect:]在绘制时,会先解码图片,再生成原始分辨率大小的bitmap,这会占用很大的内存,并且还有位数对齐等耗时操作。目前我知道的较好方法是使用ImageIO接口,避免在改变图片大小的过程中产生临时的bitmap。

两种方法的效率区别

一般我们要决定使用哪种方法的时候,首先都是看哪种方法的效率比较高,那么我们现在比较这两种方法的效率。

测试代码:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{

NSMutableArray *muAry = [NSMutableArray new];

NSTimeInterval timeBegin = [[NSDate date] timeIntervalSince1970];

for(int i=0; i<200; i+=1){ // 循环两百次

@autoreleasepool{ // 这里注意,需要加上autoreleasepool,具体原因等下说明

int index = i%5; // 我在项目放了五张图片

NSString *strName = [NSString stringWithFormat:@"temp%i", index];

NSString *strFilePath = [[NSBundle mainBundle] pathForResource:strName ofType:@"jpg"];

NSData *data = [NSData dataWithContentsOfFile:strFilePath];

UIImage *img = [self.class getThumImgOfConextWithData:data withMaxPixelSize:500]; // ImageContext 方法

// UIImage *img = [self.class getThumImgOfImgIOWithData:data withMaxPixelSize:500]; // Image I/O方法

[muAry addObject:img];

data = nil;

strFilePath = nil;

}

}

NSTimeInterval timeEnd = [[NSDate date] timeIntervalSince1970];

NSLog(@"耗费时间:%f", timeEnd - timeBegin);// 处理耗费时间

});

模拟器上测试,输出结果:

/** ImageContext */

2018-03-07 15:58:38.836944+0800 Demo[39119:3623621] 耗费时间:6.395285

/** Image I/O */

2018-03-07 15:59:35.482825+0800 JDDemo[39144:3626712] 耗费时间:6.306523

从时间看,两种方法的效率其实是差不多的,看样子用哪种方法都可以的。

但是,需要注意一点!!!

ImageContext有一个很严重的问题

那就是占用内存!

首先,你可以注意到上面的测试代码,我在for循环里面添加了@autoreleasepool,你可以把他去掉再运行试试。

屏幕快照 2018-03-07 16.05.38.png

运行占用内存Memory可以随时让你的App say goodbye ! !!

为什么会出现这种情况呢,接下来我用Time Profiler分析一下。

屏幕快照 2018-03-07 16.13.44.png

从调用的方法可以看到,ImageContext方法的drawInRect底层也是使用image I/O 对图片进行处理。Image I/O函数会创建一个图片数据对象保存,但是关闭ImageContext我们只有一个方法:UIGraphicsEndImageContext。那么我们来看看这个方法干了什么。

屏幕快照 2018-03-07 16.19.22.png

可以看到,这个方法仅仅是把Context对象从栈顶释放,却没有释放我们的图片内存数据,怪不得内存那么高!!!

那么为什么添加了@autoreleasepool就可以解决了呢,我推测是底层代码对图片数据对象 添加了 autorelease 标识,那么他就会添加到最近的 autoreleasepool 中。(如果你不手动添加一层autoreleasepool,那么就会添加到dispatch_async自动添加的autoreleasepool,这个需要等子线程运行结束才会被释放,关于autoreleasepool可以看我的这篇文章:https://www.jianshu.com/p/61d8131c6bf3)

以图为证:(没有手动添加@autoreleasepool的情况)

屏幕快照 2018-03-07 16.27.15.png

这就搞明白了为什么运行时内存那么高啦,因为所有图片的数据对象要等到子线程运行结束后才会释放!

那么我们添加@autoreleasepool在for内,然后运行看看 autoreleasepool 做了什么处理

屏幕快照 2018-03-07 16.31.53.png

放上drawInRect的细节图对比更清晰

屏幕快照 2018-03-07 16.36.25.png

好啦,大概明白为什么要加一层@autoreleasepool了吧,不过再深究是不是再imageIO_Malloc导致的占用内存,我就搞不明白啦,毕竟水平有限,我也看着很头疼…

那么为什么用Image I/O没有这个问题呢

因为,我们已经手动调用了CFRelease

CFRelease(src);

CFRelease(thumbnail);

最后说明一下,这篇是我自己找方法监测的,可能存在有错误的地方,如果大神们发现了,请告诉我一声呗,不胜感激!!!

2018.10.09 后续

最近在看资料CoreImage的时候,看到了CoreImage也有一种方法可以进行图片尺寸,那就是利用CIFilter滤镜。

3. CoreImage

/** CoreImage 获取指定尺寸的图片,返回的结果Image 目标尺寸大小 <= 图片原始尺寸大小

@param data ---- 图片Data

@param maxPixelSize ---- 图片最大宽/高尺寸 ,设置后图片会根据最大宽/高 来等比例缩放图片

@return 目标尺寸的图片Image */

+ (UIImage*) getThumImgOfCIWithData:(NSData*)data withMaxPixelSize:(int)maxPixelSize{

UIImage *imgResult = nil;

if(data == nil) { return imgResult; }

if(data.length <= 0) { return imgResult; }

if(maxPixelSize <= 0) { return imgResult; }

const float scale = [UIScreen mainScreen].scale;

CIImage *imgInput = [CIImage imageWithData:data];

if(imgInput == nil) { return imgResult; }

const float maxSizeTo = scale * maxPixelSize;

float scaleHandle = 0;

CGSize sizeImg = imgInput.extent.size;

if(sizeImg.width > sizeImg.height){ // 根据最大的宽/高 值,等比例计算出最终目标尺寸

scaleHandle = maxSizeTo / sizeImg.width;

} else {

scaleHandle = maxSizeTo / sizeImg.height;

}

if(scaleHandle > 1.0){

scaleHandle = 1.0;

}

CIFilter *filter = [CIFilter filterWithName:@"CILanczosScaleTransform"];

[filter setValue:imgInput forKey:kCIInputImageKey];

[filter setValue:@(scaleHandle) forKey:kCIInputScaleKey]; // 设置图片的缩放比例

CIImage *imgOuput = [filter valueForKey:kCIOutputImageKey];

if(imgOuput != nil){ // 此时imgOuput属于CIImage,不能直接通过CPU渲染到屏幕上,需要一个中间对象进行转换

// 方法1:CIContext

NSDictionary *dicOptions = @{kCIContextUseSoftwareRenderer : @(YES)}; // kCIContextUseSoftwareRenderer 默认YES,设置YES是创建基于GPU的CIContext对象,效率要比CPU高很多。

CIContext *context = [CIContext contextWithOptions:dicOptions];

CGImageRef imgRef = [context createCGImage:imgOuput fromRect:imgOuput.extent];

imgResult = [UIImage imageWithCGImage:imgRef scale:scale orientation:UIImageOrientationUp];

// 方法2: [UIImage imageWithCIImage:]生成UIImage,但是这个方法不能指定CIContext的设置

// imgResult = [UIImage imageWithCIImage:imgOuput scale:scale orientation:UIImageOrientationUp];

/* ========================================================

方法1和2的区别在于,方法1把图片渲染到屏幕的准备工作已经提前完成了,CPU可以直接把结果图片显示到图片上;

而方法2则是把屏幕渲染工作推迟到了图片真正显示到屏幕的时候才进行,会卡住主线程的。

======================================================== */

}

return imgResult;

}

不过CIFilter的主要问题在于,虽然其处理图片渲染很强大,但是在进行图片尺寸缩放的操作时会比较耗时,明显比ImageI/O和UIKit慢,所以这个方法仅仅只是说明一下,在处理图片尺寸时优先选用ImageI/O。

最后这是我做方法对比时写的demo结果截图(把原图压缩到100时各个方法的图片内存大小)。

Simulator Screen Shot - iPhone 6s - 2018-10-09 at 14.22.11.png

/** 获取图片在内存中占用的空间大小 */

+ (UInt64) getMemorySizeWithImg:(UIImage*)img{

UInt64 cgImageBytesPerRow = CGImageGetBytesPerRow(img.CGImage);

UInt64 cgImageHeight = CGImageGetHeight(img.CGImage);

UInt64 size = cgImageHeight * cgImageBytesPerRow;

NSLog(@"MemorySize:%lu Bytes",(unsigned long)size);

return size;

}

ios 改变图片尺寸_iOS 修改图片尺寸的方法相关推荐

  1. w3cschool php 调整图片尺寸,PHP_php修改上传图片尺寸的方法,本文实例讲述了php修改上传图 - phpStudy...

    php修改上传图片尺寸的方法 本文实例讲述了php修改上传图片尺寸的方法.分享给大家供大家参考.具体实现方法如下: // This is the temporary file created by P ...

  2. java 修改图片分辨率_java 修改图片的像素大小,清晰度

    修改图片的像素,清晰度 代码 /** * 改变图片 像素 * * @param file * @param qality 参数qality是取值0~1范围内 清晰程度 数值越小分辨率越低 * @par ...

  3. python修改图片,Python之修改图片像素值的方法

    在做语义分割项目时,标注的图片不合标准,而且类型是RGBA型,且是A的部分表示的类别,因此需要将该图片转化为RGB图片 # -*- coding:utf8 -*- import os from PIL ...

  4. qimage加载bmp图片_批量修改图片大小,我发现了最简单的方法!

    点击上方蓝字  关注我们 批量调整图片大小的软件很多很多,但很多时候我仅仅对图片做些小修改,而不需要那么多强大的功能,要的是实用,而不是有多华丽,然后运行速度快,也是我们所追求的,不要批处理图片的时候 ...

  5. QT显示图片和中途修改图片

    使用qlabel label->setPixmap(QPixmap("./pic.jpg")); 更换图片 qimage实现 QImage *image= new QImag ...

  6. php网站修改图片大小,php 修改图片大小

    set_time_limit(0); ini_set("memory_limit","500M"); $dir = dir('./'); while (fals ...

  7. C#修改图片尺寸,不改变原有图片比例

    C#使用BitMap修改图片尺寸,修改图片大小,不改变原有图片比例 修改后图片  代码 public static void image(){System.Drawing.Image img = Sy ...

  8. 如何改变图片大小kb?图片尺寸怎么在线修改?

    随着现在拍摄设备的像素越来越高,图片越来越清楚也就让图片的体积越来越大,在使用这些图片素材的时候就经常会遇到图片太大无法发送或者上传的情况,那么这时候应该怎么缩小图片的大小kb呢?今天来教给大家一个图 ...

  9. 图片尺寸太大?教你用简单的方法轻松修改图片大小

    在使用图片的时候碰到图片尺寸过大超出限制的情况要怎么办呢?这种问题大家应该都碰到过,这时候就需要来缩减图片的尺寸以达到系统的要求才能正常使用图片,那么修改图片大小(https://www.yasuot ...

  10. C# 批量修改图片尺寸和DPI

    读取指定路径下的图片文件,修改图片尺寸和pdi 我的文件夹下都是图片, 如果是混合的文件,自己写个后缀名过滤函数就行,或者修改  GetFiles 过滤参数 "*". string ...

最新文章

  1. 5年Python功力,总结了10个开发技巧
  2. 迷你DVD管理器项目
  3. python3.8安装pygame_Python3.8安装Pygame教程步骤详解
  4. 无法启动此程序,因为计算机中丢失msvcrtd.dll,Win7打开剑灵提示“丢失d3dx10_43.dll、MSVCRTD.dll文件”怎么办?...
  5. 基于RT-Thread实现的小游戏(贪吃蛇、俄罗斯方块)
  6. python数据结构《排序专题复习》
  7. C语言自加自减运算符(++i / i++) - C语言零基础入门教程
  8. [原创]软件测试工具简介及下载地址(不定时更新)
  9. 车票?工作?对象?Python 教你优雅解决年关三大难题!
  10. 安装Discuz!NT(SQL Server 2005)
  11. 麒麟系统安装打印机共享_银河麒麟 惠普打印机驱动怎么安装
  12. 2-java面向对象
  13. 相似图片搜索——感知哈希算法
  14. 初识Web与HTML
  15. CF Div2 781
  16. SpringBoot - OAuth2第三方登录之新浪微博登录
  17. 多尺度计算机模拟方法,多尺度问题的数值模拟.pdf
  18. 16.引言篇——自定义过滤器及标签
  19. redis击穿、雪崩、穿透解决方案
  20. [jzoj4210] 【五校联考1day1】我才不是萝莉控呢 {哈夫曼树}

热门文章

  1. 市场调研报告-电动汽车充电插座市场现状及未来发展趋势
  2. python 输入整数数组_Python 2中的整数数组输入
  3. 服务器装系统鼠标键盘不能动,装系统鼠标键盘不能动
  4. dns服务器优化 360,360DNS优化
  5. 基于Java swing+mysql+eclipse的【图书管理系统】
  6. springboot jpa 实体类继承
  7. java版我的世界MITE怎么下_我的世界mite振金版
  8. 调用高德地图、百度地图客户端
  9. python利用蒙版抠图(使用PIL.Image和cv2)输出透明背景图
  10. Ubuntu 16.04 安装 NVIDIA GeForce GTX 1060 显卡驱动,以及 CUDA 10.1