iOS 修改圖片尺寸的方法

目前在iOS上對(duì)于圖片的優(yōu)化點(diǎn)有很多饲常,例如圖片解碼唇撬、圖片漸加載和圖片尺寸處理。這篇文章是說(shuō)明目前iOS 代碼中修改圖片尺寸的兩種方法,以及這兩種方法區(qū)別和注意點(diǎn)澎迎。

修改圖片尺寸的兩種方法

1. 畫布ImageContext(UIKit)

/** 利用畫布對(duì)圖片尺寸進(jìn)行修改
 @param data ---- 圖片Data
 @param maxPixelSize ---- 圖片最大寬/高尺寸 ,設(shè)置后圖片會(huì)根據(jù)最大寬/高 來(lái)等比例縮放圖片

 @return 目標(biāo)尺寸的圖片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){ // 根據(jù)最大的寬/高 值球凰,等比例計(jì)算出最終目標(biāo)尺寸
        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 創(chuàng)建省略圖

/** Image I/O 獲取指定尺寸的圖片凛膏,返回的結(jié)果Image 目標(biāo)尺寸大小 <= 圖片原始尺寸大小
 @param data ---- 圖片Data
 @param maxPixelSize ---- 圖片最大寬/高尺寸 ,設(shè)置后圖片會(huì)根據(jù)最大寬/高 來(lái)等比例縮放圖片

 @return 目標(biāo)尺寸的圖片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的鍵值說(shuō)明
     kCGImageSourceCreateThumbnailWithTransform - 設(shè)置縮略圖是否進(jìn)行Transfrom變換
     kCGImageSourceCreateThumbnailFromImageAlways - 設(shè)置是否創(chuàng)建縮略圖熊经,無(wú)論原圖像有沒(méi)有包含縮略圖荠察,默認(rèn)kCFBooleanFalse,影響 CGImageSourceCreateThumbnailAtIndex 方法
     kCGImageSourceCreateThumbnailFromImageIfAbsent - 設(shè)置是否創(chuàng)建縮略圖奈搜,如果原圖像有沒(méi)有包含縮略圖悉盆,則創(chuàng)建縮略圖,默認(rèn)kCFBooleanFalse馋吗,影響 CGImageSourceCreateThumbnailAtIndex 方法
     kCGImageSourceThumbnailMaxPixelSize - 設(shè)置縮略圖的最大寬/高尺寸 需要設(shè)置為CFNumber值焕盟,設(shè)置后圖片會(huì)根據(jù)最大寬/高 來(lái)等比例縮放圖片
     kCGImageSourceShouldCache - 設(shè)置是否以解碼的方式讀取圖片數(shù)據(jù) 默認(rèn)為kCFBooleanTrue,如果設(shè)置為true宏粤,在讀取數(shù)據(jù)時(shí)就進(jìn)行解碼 如果為false 則在渲染時(shí)才進(jìn)行解碼 */
    CFDictionaryRef dicOptionsRef = (__bridge CFDictionaryRef) @{
                                                                 (id)kCGImageSourceCreateThumbnailFromImageIfAbsent : @(YES),
                                                                 (id)kCGImageSourceThumbnailMaxPixelSize : @(sizeTo),
                                                                 (id)kCGImageSourceShouldCache : @(YES),
                                                                 };
    CGImageSourceRef src = CGImageSourceCreateWithData(dataRef, nil);
    CGImageRef thumImg = CGImageSourceCreateThumbnailAtIndex(src, 0, dicOptionsRef); //注意:如果設(shè)置 kCGImageSourceCreateThumbnailFromImageIfAbsent為 NO脚翘,那么 CGImageSourceCreateThumbnailAtIndex 會(huì)返回nil
    
    CFRelease(src); // 注意釋放對(duì)象,否則會(huì)產(chǎn)生內(nèi)存泄露
    
    imgResult = [UIImage imageWithCGImage:thumImg scale:scale orientation:UIImageOrientationUp];
    
    if(thumImg != nil){
        CFRelease(thumImg); // 注意釋放對(duì)象绍哎,否則會(huì)產(chǎn)生內(nèi)存泄露
    }
    
    return imgResult;
}

需要注意的是来农, 使用Image I/O 時(shí),設(shè)置kCGImageSourceThumbnailMaxPixelSize 的最大高/寬值時(shí)崇堰,如果設(shè)置值超過(guò)了圖片文件原本的高/寬值沃于,那么CGImageSourceCreateThumbnailAtIndex獲取的圖片尺寸將是原始圖片文件的尺寸。比如海诲,設(shè)置 kCGImageSourceThumbnailMaxPixelSize 為600繁莹,而如果圖片文件尺寸為580*212,那么最終獲取到的圖片尺寸是580 * 212特幔。


小注釋:UIKit處理很大的圖片時(shí)咨演,容易出現(xiàn)內(nèi)存崩潰(超過(guò)App可使用內(nèi)存的上限),原因是[UIImage drawInRect:]在繪制時(shí)蚯斯,會(huì)先解碼圖片薄风,再生成原始分辨率大小的bitmap,這會(huì)占用很大的內(nèi)存拍嵌,并且還有位數(shù)對(duì)齊等耗時(shí)操作遭赂。目前我知道的較好方法是使用ImageIO接口,避免在改變圖片大小的過(guò)程中產(chǎn)生臨時(shí)的bitmap撰茎。


兩種方法的效率區(qū)別

一般我們要決定使用哪種方法的時(shí)候嵌牺,首先都是看哪種方法的效率比較高,那么我們現(xiàn)在比較這兩種方法的效率。

測(cè)試代碼:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSMutableArray<UIImage*> *muAry = [NSMutableArray new];
        NSTimeInterval timeBegin = [[NSDate date] timeIntervalSince1970];
        for(int i=0; i<200; i+=1){ // 循環(huán)兩百次
            @autoreleasepool{ // 這里注意逆粹,需要加上autoreleasepool募疮,具體原因等下說(shuō)明
                int index = i%5; // 我在項(xiàng)目放了五張圖片
                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èi)時(shí)間:%f", timeEnd - timeBegin);// 處理耗費(fèi)時(shí)間
    });

模擬器上測(cè)試,輸出結(jié)果:

/** ImageContext */
2018-03-07 15:58:38.836944+0800 Demo[39119:3623621] 耗費(fèi)時(shí)間:6.395285

/** Image I/O */
2018-03-07 15:59:35.482825+0800 JDDemo[39144:3626712] 耗費(fèi)時(shí)間:6.306523

從時(shí)間看僻弹,兩種方法的效率其實(shí)是差不多的阿浓,看樣子用哪種方法都可以的。


但是蹋绽,需要注意一點(diǎn)0疟小!卸耘!
ImageContext有一個(gè)很嚴(yán)重的問(wèn)題
那就是占用內(nèi)存退敦!

首先,你可以注意到上面的測(cè)試代碼蚣抗,我在for循環(huán)里面添加了@autoreleasepool侈百,你可以把他去掉再運(yùn)行試試。


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

運(yùn)行占用內(nèi)存Memory可以隨時(shí)讓你的App say goodbye 翰铡! 6塾颉!
為什么會(huì)出現(xiàn)這種情況呢锭魔,接下來(lái)我用Time Profiler分析一下例证。


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

從調(diào)用的方法可以看到,ImageContext方法的drawInRect底層也是使用image I/O 對(duì)圖片進(jìn)行處理迷捧。Image I/O函數(shù)會(huì)創(chuàng)建一個(gè)圖片數(shù)據(jù)對(duì)象保存织咧,但是關(guān)閉ImageContext我們只有一個(gè)方法:UIGraphicsEndImageContext。那么我們來(lái)看看這個(gè)方法干了什么党涕。

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

可以看到烦感,這個(gè)方法僅僅是把Context對(duì)象從棧頂釋放巡社,卻沒(méi)有釋放我們的圖片內(nèi)存數(shù)據(jù)膛堤,怪不得內(nèi)存那么高!I胃谩肥荔!

那么為什么添加了@autoreleasepool就可以解決了呢,我推測(cè)是底層代碼對(duì)圖片數(shù)據(jù)對(duì)象 添加了 autorelease 標(biāo)識(shí)朝群,那么他就會(huì)添加到最近的 autoreleasepool 中燕耿。(如果你不手動(dòng)添加一層autoreleasepool,那么就會(huì)添加到dispatch_async自動(dòng)添加的autoreleasepool姜胖,這個(gè)需要等子線程運(yùn)行結(jié)束才會(huì)被釋放誉帅,關(guān)于autoreleasepool可以看我的這篇文章:http://www.reibang.com/p/61d8131c6bf3
以圖為證:(沒(méi)有手動(dòng)添加@autoreleasepool的情況)

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

這就搞明白了為什么運(yùn)行時(shí)內(nèi)存那么高啦,因?yàn)樗袌D片的數(shù)據(jù)對(duì)象要等到子線程運(yùn)行結(jié)束后才會(huì)釋放!
那么我們添加@autoreleasepool在for內(nèi)蚜锨,然后運(yùn)行看看 autoreleasepool 做了什么處理


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

放上drawInRect的細(xì)節(jié)圖對(duì)比更清晰


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

好啦档插,大概明白為什么要加一層@autoreleasepool了吧,不過(guò)再深究是不是再imageIO_Malloc導(dǎo)致的占用內(nèi)存亚再,我就搞不明白啦郭膛,畢竟水平有限,我也看著很頭疼…

那么為什么用Image I/O沒(méi)有這個(gè)問(wèn)題呢
因?yàn)榉招覀円呀?jīng)手動(dòng)調(diào)用了CFRelease

CFRelease(src);
CFRelease(thumbnail);

最后說(shuō)明一下则剃,這篇是我自己找方法監(jiān)測(cè)的,可能存在有錯(cuò)誤的地方如捅,如果大神們發(fā)現(xiàn)了棍现,請(qǐng)告訴我一聲唄,不勝感激>登病V嵩邸!


2018.10.09 后續(xù)

最近在看資料CoreImage的時(shí)候烈涮,看到了CoreImage也有一種方法可以進(jìn)行圖片尺寸朴肺,那就是利用CIFilter濾鏡。

3. CoreImage

/** CoreImage 獲取指定尺寸的圖片坚洽,返回的結(jié)果Image 目標(biāo)尺寸大小 <= 圖片原始尺寸大小
 @param data ---- 圖片Data
 @param maxPixelSize ---- 圖片最大寬/高尺寸 戈稿,設(shè)置后圖片會(huì)根據(jù)最大寬/高 來(lái)等比例縮放圖片
 
 @return 目標(biāo)尺寸的圖片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){ // 根據(jù)最大的寬/高 值,等比例計(jì)算出最終目標(biāo)尺寸
        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]; // 設(shè)置圖片的縮放比例
    CIImage *imgOuput = [filter valueForKey:kCIOutputImageKey];
    if(imgOuput != nil){ // 此時(shí)imgOuput屬于CIImage讶舰,不能直接通過(guò)CPU渲染到屏幕上鞍盗,需要一個(gè)中間對(duì)象進(jìn)行轉(zhuǎn)換
        
        // 方法1:CIContext
        NSDictionary *dicOptions = @{kCIContextUseSoftwareRenderer : @(YES)}; // kCIContextUseSoftwareRenderer 默認(rèn)YES,設(shè)置YES是創(chuàng)建基于GPU的CIContext對(duì)象跳昼,效率要比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,但是這個(gè)方法不能指定CIContext的設(shè)置
//        imgResult = [UIImage imageWithCIImage:imgOuput scale:scale orientation:UIImageOrientationUp];
        
        /* ========================================================
         方法1和2的區(qū)別在于鹅颊,方法1把圖片渲染到屏幕的準(zhǔn)備工作已經(jīng)提前完成了敷存,CPU可以直接把結(jié)果圖片顯示到圖片上;
         而方法2則是把屏幕渲染工作推遲到了圖片真正顯示到屏幕的時(shí)候才進(jìn)行堪伍,會(huì)卡住主線程的锚烦。
          ======================================================== */
    }
    
    return imgResult;
}

不過(guò)CIFilter的主要問(wèn)題在于,雖然其處理圖片渲染很強(qiáng)大帝雇,但是在進(jìn)行圖片尺寸縮放的操作時(shí)會(huì)比較耗時(shí)涮俄,明顯比ImageI/O和UIKit慢,所以這個(gè)方法僅僅只是說(shuō)明一下尸闸,在處理圖片尺寸時(shí)優(yōu)先選用ImageI/O彻亲。

最后這是我做方法對(duì)比時(shí)寫的demo結(jié)果截圖(把原圖壓縮到100時(shí)各個(gè)方法的圖片內(nèi)存大性谐)。


Simulator Screen Shot - iPhone 6s - 2018-10-09 at 14.22.11.png
/** 獲取圖片在內(nèi)存中占用的空間大小 */
+ (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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苞尝,一起剝皮案震驚了整個(gè)濱河市硫惕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌野来,老刑警劉巖恼除,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異曼氛,居然都是意外死亡豁辉,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門舀患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)徽级,“玉大人,你說(shuō)我怎么就攤上這事聊浅〔颓溃” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵低匙,是天一觀的道長(zhǎng)旷痕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)顽冶,這世上最難降的妖魔是什么欺抗? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮强重,結(jié)果婚禮上绞呈,老公的妹妹穿的比我還像新娘。我一直安慰自己间景,他們只是感情好佃声,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著倘要,像睡著了一般圾亏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上碗誉,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天召嘶,我揣著相機(jī)與錄音,去河邊找鬼哮缺。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甲喝,可吹牛的內(nèi)容都是我干的尝苇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼糠溜!你這毒婦竟也來(lái)了淳玩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤非竿,失蹤者是張志新(化名)和其女友劉穎蜕着,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體红柱,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡承匣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锤悄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片韧骗。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖零聚,靈堂內(nèi)的尸體忽然破棺而出袍暴,到底是詐尸還是另有隱情,我是刑警寧澤隶症,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布政模,位于F島的核電站,受9級(jí)特大地震影響蚂会,放射性物質(zhì)發(fā)生泄漏览徒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一颂龙、第九天 我趴在偏房一處隱蔽的房頂上張望习蓬。 院中可真熱鬧,春花似錦措嵌、人聲如沸躲叼。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)枫慷。三九已至,卻和暖如春浪规,著一層夾襖步出監(jiān)牢的瞬間或听,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工笋婿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留誉裆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓缸濒,卻偏偏與公主長(zhǎng)得像足丢,于是被迫代替她去往敵國(guó)和親粱腻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,525評(píng)論 25 707
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,125評(píng)論 29 470
  • 中國(guó)特色小鎮(zhèn)操盤手與投資人全球峰會(huì)在北京朗麗茲西山花園酒店舉辦,此次峰會(huì)很榮幸邀請(qǐng)到了國(guó)家發(fā)改委國(guó)際合作中心投融資...
    Hcxzh小鎮(zhèn)閱讀 428評(píng)論 0 3
  • 01 大學(xué)里最后一個(gè)暑假耀鸦,打著學(xué)車的旗號(hào)在家里得過(guò)且過(guò)柬批。 晚上八點(diǎn)黃金檔,我在電視機(jī)前對(duì)每個(gè)臺(tái)的節(jié)目進(jìn)行掃射袖订。微信...
    一只螃蟹367閱讀 312評(píng)論 0 0
  • 昨晚知道專業(yè)課成績(jī)氮帐,自己差的太多了。很失望吧著角!父母也很難受揪漩,今天一上午都在頹廢,感覺(jué)自己一無(wú)所有了±艨冢現(xiàn)在想想這只是...
    賞心悅事閱讀 207評(píng)論 0 0