OC:image的加載.解壓.渲染繪制(Quartz異步繪制圖片)

一搪花、關(guān)于圖片的兩種格式,PNG和JPEG

圖片文件被加載后必須要進(jìn)行解碼炒辉,解碼過程是一個(gè)相當(dāng)復(fù)雜的任務(wù),需要消耗非常長的時(shí)間泉手。解碼后的圖片將同樣使用相當(dāng)大的內(nèi)存黔寇。

用于加載的CPU時(shí)間相對(duì)于解碼來說根據(jù)圖片格式而不同。對(duì)于iOS來說大多處理的就是PNG和JPEG了斩萌。

  • JPEG可以非常好的壓縮圖片缝裤,但是JPEG是失真的,它去除了人眼很難察覺的信息颊郎,并且這樣做可以超出像gzip這樣壓縮算法的限制憋飞,但是壓縮算法也更復(fù)雜。相對(duì)來說姆吭,JPEG圖片更小榛做,加載更快,但是解壓的步驟要消耗更長的時(shí)間。
  • 對(duì)于PNG來說它的壓縮是無損的检眯,所以圖片相對(duì)JPEG來說也大很多升敲,加載時(shí)間會(huì)比JPEG更長,但是解碼會(huì)相對(duì)較快轰传,而且Xcode會(huì)把PNG圖片進(jìn)行解碼優(yōu)化之后引入工程驴党,當(dāng)Xcode優(yōu)化一個(gè) PNG 文件的時(shí)候,它將 PNG 文件變成一個(gè)從技術(shù)上講不再是有效的PNG文件获茬。但是 iOS 可以讀取這種文件港庄,并且這比解壓縮正常的 PNG 文件更快。

當(dāng)加載圖片的時(shí)候恕曲,iOS通常會(huì)延遲解壓圖片的時(shí)間鹏氧,直到加載到內(nèi)存之后。這就會(huì)在準(zhǔn)備繪制圖片的時(shí)候影響性能佩谣,因?yàn)樾枰诶L制之前進(jìn)行解壓(通常是消耗時(shí)間的問題所在)把还。

二、圖片的加載

圖片的加載的幾種方法茸俭,這里只說兩種:

  • 使用緩存吊履,多處重復(fù)使用的圖片使用imageNamed方法:
    + (nullable UIImage *)imageNamed:(NSString *)name;
    此方法避免延時(shí)解壓,會(huì)在加載圖片之后立刻進(jìn)行解壓调鬓。不過+imageNamed:只對(duì)從應(yīng)用資源中的圖片有效艇炎,所以對(duì)用戶生成的圖片內(nèi)容或者是下載的圖片就沒法使用了。

    另外腾窝,此方法會(huì)在系統(tǒng)緩存中查找具有指定名稱的圖像對(duì)象缀踪,并在存在時(shí)返回該對(duì)象。如果匹配的圖像對(duì)象尚未在緩存中虹脯,該方法根據(jù)指定的圖片名加載圖片并解壓驴娃,緩存它,然后返回生成的對(duì)象循集。
    在nib文件中引用的圖片同樣也是這個(gè)機(jī)制唇敞,所以你很多時(shí)候都在隱式的使用它。

  • 大圖暇榴,且就加載一次的圖片:
    + (nullable UIImage *)imageWithContentsOfFile:(NSString *)path;
    如果圖片只顯示一次厚棵,并且希望它不被添加到系統(tǒng)的緩存中,那么應(yīng)該使用imageWithContentsOfFile:蔼紧。此方法不應(yīng)用系統(tǒng)圖片緩存婆硬,對(duì)于很多只加載單次的圖片來說,使用此方法可以潛在的改善內(nèi)存使用奸例。

在說圖片的解壓和渲染繪制之前彬犯,我們先說下CoreGraphics中的一些我們要用到的方法等向楼。

1.Graphics Context(圖形上下文)類型:

  • View Graphics Context :iOS中的繪圖圖形上下文,在drawRect方法中可以通過UIGraphicsGetCurrentContext獲得谐区。
  • Window Graphics Context :Mac OS X中的圖形上下文湖蜕,不多說。
  • PDF Graphics Context :用于創(chuàng)建PDF文檔的圖形上下文宋列。
  • Bitmap Graphics Context :位圖圖形上下文昭抒。
  • Graphics Context for Printing:為打印而來的圖形上下文。

2.坐標(biāo)系:
UIKit默認(rèn)是左上角零點(diǎn)炼杖,y向下的坐標(biāo)系灭返。
而Quartz是默認(rèn)左下角零點(diǎn),y向上的坐標(biāo)系坤邪。

一般情況下熙含,對(duì)于我們經(jīng)常使用的Bitmap Context,如果其對(duì)應(yīng)一個(gè)視圖艇纺,視圖在屏幕上的就是y下坐標(biāo)系怎静,在屏幕外的是y上坐標(biāo)系。

日常使用中需要注意:

  • UIGraphicsBeginImageContext黔衡、UIGraphicsBeginImageContextWithOptions:獲取到的是y向下坐標(biāo)系蚓聘,文檔中指出如果應(yīng)用程序通過調(diào)用函數(shù)UIGraphicsBeginImageContextWithOptions創(chuàng)建圖形上下文,則UIKit將相同的變換應(yīng)用于上下文的坐標(biāo)系员帮,就像UIView對(duì)象的圖形上下文一樣或粮,所以是y向下的。

  • CGContextDrawImage:默認(rèn)使用y上坐標(biāo)系來繪圖捞高。

  • CGBitmapContextCreate:創(chuàng)建的bitmapContext默認(rèn)是y上坐標(biāo)系。雖然是y上坐標(biāo)系渣锦,但是如果是單純繪制圖片硝岗,bitmapContext加載到UIKit視圖上時(shí)會(huì)自動(dòng)轉(zhuǎn)換坐標(biāo)系,所以不需要手動(dòng)變換坐標(biāo)袋毙。

3.再來看CGImage的創(chuàng)建

CGImageRef __nullable CGImageCreate(
  size_t width, size_t height,

  size_t bitsPerComponent, 
  size_t bitsPerPixel, 
  size_t bytesPerRow,
 
  CGColorSpaceRef cg_nullable space, 
  CGBitmapInfo bitmapInfo,
  CGDataProviderRef cg_nullable provider,

  const CGFloat * __nullable decode, 
  bool shouldInterpolate,
 CGColorRenderingIntent intent
)CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

參數(shù)真的是多型檀,一個(gè)一個(gè)看:

  • width,heigth:這個(gè)不用多想,就是圖片的寬高听盖,單位是像素胀溺。

  • bitsPerComponent:要知道在RGB中,顏色有4個(gè)分量(R皆看、G仓坞、B、A)腰吟,這個(gè)參數(shù)表示單個(gè)分量所占位數(shù)无埃,再iOS上顏色空間CGColorSpaceGetDeviceRGB()對(duì)應(yīng)的是8位徙瓶。

  • bitsPerPixel:表示一個(gè)像素占多少位 = (bitsPerComponent * number of components + 7)/8 * 8,RGB的一個(gè)像素由4個(gè)顏色分量組成嫉称,每個(gè)分量通常是8位侦镇,那么一般單個(gè)像素總共占32位(有些RGB沒有A,不是8位)织阅。如果某個(gè)空間中每個(gè)分量是5位壳繁,由4個(gè)分量組成,則一個(gè)像素占24位荔棉。

  • bytesPerRow:表示一行像素的總字節(jié)數(shù) = width * (bitsPerPixel / BYTE_SIZE)闹炉,注意這里是字節(jié)(通常1字節(jié)=8位)。

  • space:顏色空間江耀,比如顏色空間RGB中(1,0,0)表示紅色剩胁,比如顏色空間BGR(1,0,0)表示藍(lán)色,同一個(gè)顏色在不同顏色空間里有不同的表達(dá)式祥国。需要手動(dòng)release昵观。

  • bitmapInfo:位圖的布局信息,枚舉舌稀,使用(CGImageAlphaInfo | CGBitmapInfo)啊犬。當(dāng)在ARGB-32下,并且opaque是YES即不包含alpha時(shí)壁查,使用(kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)表示沒有alpha并跳過第一個(gè)顏色分量觉至,位圖排序使用“主機(jī)32位序”,這是一個(gè)宏睡腿,在iPhone中通常是‘小端32位序’语御。需要提一下UIGraphicsBeginImageContext返回的context的顏色空間就是32位RGB,顏色分量8位席怪。

  • provider:數(shù)據(jù)提供者应闯,這個(gè)主要是提供圖片數(shù)據(jù)的。如果提供的是未解壓到圖片數(shù)據(jù)挂捻,那么創(chuàng)建的就是未解壓的圖片碉纺;如果提供的是解壓后的數(shù)據(jù),那么創(chuàng)建的就是解壓后的圖片刻撒。

  • decode:解碼數(shù)組骨田,這個(gè)解碼與圖片的解壓是兩回事,不要混淆了声怔。圖像的解碼映射數(shù)組态贤。如果您不想允許重新映射圖像的顏色值,請(qǐng)傳遞NULL解碼數(shù)組捧搞。對(duì)于圖像顏色空間中的每個(gè)顏色分量(包括alpha分量)抵卫,解碼數(shù)組提供兩組狮荔,即一對(duì)表示范圍上限和下限的值。例如介粘,RGB顏色空間中的源圖像的解碼數(shù)組總共包含六個(gè)分量殖氏,每組由紅色,綠色和藍(lán)色組成,左三個(gè)分量是最小值姻采,右三個(gè)分量是最大值雅采。渲染圖像時(shí),Core Graphics使用線性變換將原始組件值映射到指定范圍內(nèi)適合目標(biāo)顏色空間的相對(duì)數(shù)字慨亲。

      • 假設(shè)原色值是藍(lán)色(0,0,1)婚瓜,decode設(shè)置{0.5,1,0.8},相當(dāng)于{0.5,1,0.8,0,0,0}刑棵,色值會(huì)先判斷如果小于最小值巴刻,那么直接取最小值,再判斷如果大于最大值蛉签,那么直接取最大值胡陪,那么得出的結(jié)論就是:色值(0,0,1)變成了(0.5, 1, 0)。
  • shouldInterpolate:一個(gè)布爾值碍舍,指定是否應(yīng)進(jìn)行插值柠座。插值設(shè)置指定Core Graphics是否要對(duì)圖像應(yīng)用像素平滑算法。在沒有插值的情況下片橡,當(dāng)在具有比圖像數(shù)據(jù)更高分辨率的輸出設(shè)備上繪制時(shí)妈经,圖像可能呈現(xiàn)鋸齒狀或像素化。

  • intent:渲染意圖常量捧书,渲染意圖常量決定了將顏色從一個(gè)顏色空間映射到另一個(gè)顏色空間的確切方法吹泡。

CGImageAlphaInfo透明度分量信息和CGBitmapInfo位圖布局信息:

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {

 kCGImageAlphaNone,  /* For example, RGB. */
 kCGImageAlphaPremultipliedLast, /* For example, premultiplied RGBA */
 kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */
 kCGImageAlphaLast, /* For example, non-premultiplied RGBA */
 kCGImageAlphaFirst, /* For example, non-premultiplied ARGB */
 kCGImageAlphaNoneSkipLast,  /* For example, RBGX. */
 kCGImageAlphaNoneSkipFirst, /* For example, XRGB. */
 kCGImageAlphaOnly /* No color data, alpha data only */
};

typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
 kCGBitmapAlphaInfoMask = 0x1F,
 kCGBitmapFloatInfoMask = 0xF00,
 kCGBitmapFloatComponents = (1 << 8),
 kCGBitmapByteOrderMask  = kCGImageByteOrderMask,
 kCGBitmapByteOrderDefault = kCGImageByteOrderDefault,
 kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,
 kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,
 kCGBitmapByteOrder16Big = kCGImageByteOrder16Big,
 kCGBitmapByteOrder32Big = kCGImageByteOrder32Big
}

#ifdef __BIG_ENDIAN__
//[大端序](https://zh.wikipedia.org/wiki/%E5%AD%97%E8%8A%82%E5%BA%8F#.E5.A4.A7.E7.AB.AF.E5.BA.8F)
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else /* Little endian. */
//小端序
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif

CGColorRenderingIntent繪制意圖:

/* Color rendering intents. */

typedef CF_ENUM (int32_t, CGColorRenderingIntent) {
 kCGRenderingIntentDefault,
 kCGRenderingIntentAbsoluteColorimetric,
 kCGRenderingIntentRelativeColorimetric,
 kCGRenderingIntentPerceptual,
 kCGRenderingIntentSaturation
};
  • kCGRenderingIntentDefault:默認(rèn)渲染意圖。

  • kCGRenderingIntentAbsoluteColorimetric:絕對(duì)色度渲染意圖经瓷。將輸出設(shè)備顏色域外的顏色映射為輸出設(shè)備域內(nèi)與之最接近的顏色荞胡。這可以產(chǎn)生一個(gè)裁減效果,因?yàn)樯蛲獾膬蓚€(gè)不同的顏色值可能被映射為色域內(nèi)的同一個(gè)顏色值了嚎。當(dāng)圖形使用的顏色值同時(shí)包含在源色域及目標(biāo)色域內(nèi)時(shí),這種方法是最好的廊营。常用于logo或者使用專色(spot color)時(shí)歪泳。

  • kCGRenderingIntentRelativeColorimetric:相對(duì)色度渲染意圖。轉(zhuǎn)換所有的顏色(包括色域內(nèi)的)露筒,以補(bǔ)償圖形上下文的白點(diǎn)與輸出設(shè)備白點(diǎn)之間的色差呐伞。

  • kCGRenderingIntentPerceptual:可感知渲染意圖。通過壓縮圖形上下文的色域來適應(yīng)輸出設(shè)備的色域慎式,并保持源顏色空間的顏色之間的相對(duì)性伶氢。感知渲染意圖適用于相片及其它復(fù)雜的高細(xì)度圖片趟径。

  • kCGRenderingIntentSaturation:飽和度渲染意圖。把顏色轉(zhuǎn)換到輸出設(shè)備色域內(nèi)時(shí)癣防,保持顏色的相對(duì)飽和度蜗巧。結(jié)果是包含亮度、飽和度顏色的圖片蕾盯。飽和度意圖適用于生成低細(xì)度的圖片幕屹,如描述性圖表。

4.CGBitmapContext的創(chuàng)建:

CG_EXTERN CGContextRef __nullable CGBitmapContextCreate(
  void * __nullable data,
  size_t width, size_t height, 
  size_t bitsPerComponent, 
  size_t bytesPerRow,
  CGColorSpaceRef cg_nullable space, uint32_t bitmapInfo
)CG_AVAILABLE_STARTING(10.0, 2.0);

在之前介紹過的參數(shù)就不詳細(xì)說明了级遭。

  • data:用來制定位圖存儲(chǔ)空間望拖,保存位圖數(shù)據(jù)。如果設(shè)置為NULL挫鸽,那么系統(tǒng)會(huì)自動(dòng)分配一段合適的內(nèi)存并且bytesPerRow也可以設(shè)置為0说敏;如果不為空,那么bytesPerRow也不能為0丢郊,需要填寫相應(yīng)的數(shù)值盔沫,而且要記得free(data)

  • width蚂夕,height:位圖的大小迅诬,一般設(shè)置為將要繪制的圖片的大小。

  • bitsPerComponent:單個(gè)顏色分量所占位數(shù)婿牍。

  • btyesPerRow:每行所占總字節(jié)侈贷。當(dāng)data參數(shù)設(shè)置為NULL,bytesPerRow設(shè)置為0等脂,那么系統(tǒng)就會(huì)自動(dòng)計(jì)算每行的總字節(jié)數(shù)俏蛮。

  • space:顏色空間。

  • bitmapInfo:位圖的布局信息上遥。

顏色空間space

下圖是蘋果支持的顏色空間和像素格式:


屏幕快照 2018-09-07 上午10.48.08.png

顏色空間iOS支持上述的其中8種搏屑, iOS不支持與設(shè)備無關(guān)或通用的顏色空間。根據(jù)Quartz 2D的文檔所說粉楚,iOS應(yīng)用程序必須使用設(shè)備顏色空間辣恋。

設(shè)備顏色空間主要由iOS應(yīng)用程序使用,因?yàn)槠渌x項(xiàng)不可用模软。在大多數(shù)情況下伟骨,Mac OS X應(yīng)用程序應(yīng)使用通用顏色空間,而不是創(chuàng)建設(shè)備顏色空間燃异,但是携狭,一些Quartz例程要求具有設(shè)備顏色空間的圖像,例如回俐,如果調(diào)用CGImageCreateWithMask并指定圖像作為蒙版逛腿,則必須使用設(shè)備灰色顏色空間定義圖像稀并。

  • CGColorSpaceCreateDeviceGray() 用來創(chuàng)建設(shè)備灰色顏色空間
  • CGColorSpaceCreateDeviceRGB() 用來創(chuàng)建設(shè)備RGB顏色空間
  • CGColorSpaceCreateDeviceCMYK() 用來創(chuàng)建設(shè)備CMYK顏色空間

所以iOS中,我們通常使用CGColorSpaceCreateDeviceRGB()來創(chuàng)建顏色空間单默,還有bitsPerComponent和btyesPerRow要和顏色空間匹配碘举。

三、圖片的解壓

check_green.png

這是一張30x30的圖雕凹,存儲(chǔ)大小843B殴俱,

UIImage *image = [UIImage imageNamed:@"check_green.png"];
CFDataRef rawData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));

上面的代碼可以獲得圖片解壓后的大小,為3600B枚抵。解壓縮后的圖片大小與原始文件大小之間沒有任何關(guān)系线欲,而只與圖片的像素有關(guān):
解壓縮后的圖片大小 = 圖片的像素寬 30 * 圖片的像素高 30 * 每個(gè)像素所占的字節(jié)數(shù) 4

使用+imageNamed:方法進(jìn)行加載并解壓圖片,其實(shí)內(nèi)部就是使用的ImageIO框架汽摹。

1.使用ImageIO庫來加載本地image:使用kCGImageSourceShouldCache來創(chuàng)建圖片李丰,強(qiáng)制圖片立刻解壓緩存,然后在圖片的生命周期保留解壓后的版本逼泣。

    NSString *str = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/image"];

    CGDataProviderRef provider = CGDataProviderCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:str]);
    NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES};
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source1, 0,(__bridge CFDictionaryRef)options);
    UIImage *image = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CFRelease(source);


繪制圖片過程中趴泌,系統(tǒng)會(huì)對(duì)圖片自動(dòng)解壓,所以繪制圖片也算是一種解壓的方式拉庶。

2.使用UIGraphicsBeginImageContext進(jìn)行繪制圖片嗜憔,創(chuàng)建一個(gè)基于bitmap的imageContext,并把它設(shè)置成為當(dāng)前正在使用的context,然后將圖片繪制到context上氏仗。這方法在需要大量調(diào)用的時(shí)候不要用吉捶,因?yàn)槭窍到y(tǒng)釋放內(nèi)存,所以存在延遲釋放的情況皆尔,短時(shí)間內(nèi)大量調(diào)用(如tableView的滑動(dòng)異步繪制圖片)會(huì)出現(xiàn)內(nèi)存暴漲的情況:

    //創(chuàng)建一個(gè)基于bitmap的imageContext,并把位圖立即推入到圖形上下文堆棧中呐舔。坐標(biāo)系是UIKit默認(rèn)坐標(biāo)系左上角0點(diǎn)y下。
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(160, 220), YES, [UIScreen 
mainScreen].scale);
    
    //這里要記住CGContextDrawImage會(huì)默認(rèn)使用離屏bitmap的坐標(biāo)系即左下角0點(diǎn)y上,所以要變換坐標(biāo)慷蠕。
//CGContextRef ctx = UIGraphicsGetCurrentContext();
    //CGContextTranslateCTM(ctx, 0, 220);
    //CGContextScaleCTM(ctx, 1.0, -1.0);
    //CGContextDrawImage(ctx, CGRectMake(0, 0, 160, 220), img.CGImage);
    [image drawInRect:CGRectMake(0, 0, width, height)];

    UIGraphicsEndImageContext();

3.使用CGBitmapContextCreate繪制圖片珊拼,同方法1中繪制比較,根據(jù)官方文檔說明流炕,因?yàn)閎itmap context是左下角y上坐標(biāo)系澎现,如果實(shí)際繪畫中需要轉(zhuǎn)換坐標(biāo)系,不一定比imagContext繪制快每辟。但是我們其實(shí)大部分都是在屏幕外繪制圖片的昔头,所以不需要變換坐標(biāo),實(shí)際證明比方法UIGraphicsBeginImageContext繪制要快影兽。

官方文檔上示例代碼:

//Creating a bitmap graphics context
    CGImageRef imgRef = image.CGImage;
    size_t pixelsWide = CGImageGetWidth(imgRef);
    size_t pixelsHigh = CGImageGetHeight(imgRef);
    CGContextRef    context = NULL;
    CGColorSpaceRef colorSpace;
    void *          bitmapData;
    int             bitmapByteCount;
    int             bitmapBytesPerRow;
    
    bitmapBytesPerRow   = (pixelsWide * 4);
    bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);
    
    colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    void *          bitmapData;
    bitmapData = calloc( bitmapByteCount, sizeof(uint8_t) );
    if (bitmapData == NULL) {
        fprintf (stderr, "Memory not allocated!");
        return nil;
    }
    context = CGBitmapContextCreate (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,      // bits per component
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedLast);
    if (context == NULL) {
        free (bitmapData);
        fprintf (stderr, "Context not created!");
        return NULL;
    }
    CGColorSpaceRelease( colorSpace );
    return context;

//Drawing to a bitmap graphics context
    CGRect myBoundingBox;// 1
    myBoundingBox = CGRectMake (0, 0, myWidth, myHeight);// 2
    myBitmapContext = MyCreateBitmapContext (400, 300);// 3
    // ********** Your drawing code here ********** // 4
    CGContextSetRGBFillColor (myBitmapContext, 1, 0, 0, 1);
    CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 200, 100 ));
    CGContextSetRGBFillColor (myBitmapContext, 0, 0, 1, .5);
    CGContextFillRect (myBitmapContext, CGRectMake (0, 0, 100, 200 ));
    myImage = CGBitmapContextCreateImage (myBitmapContext);// 5
    CGContextDrawImage(myContext, myBoundingBox, myImage);// 6
    char *bitmapData = CGBitmapContextGetData(myBitmapContext); // 7
    CGContextRelease (myBitmapContext);// 8
    if (bitmapData) free(bitmapData); // 9
    CGImageRelease(myImage);

上面的代碼用到了bitmapData,一定要記得手動(dòng)釋放莱革。實(shí)際使用的時(shí)候峻堰,我們一般data輸入NULL就好讹开,bitmapBytesPerRow設(shè)置為0,這樣系統(tǒng)會(huì)自動(dòng)為我們分配位圖存儲(chǔ)內(nèi)存捐名,并自動(dòng)計(jì)算每行字節(jié)數(shù)旦万。

下面是我們通常使用bitmap context來繪制圖片的代碼:

+ (UIImage *)imageDecodeByBitmap:(UIImage *)image
{
    //創(chuàng)建一個(gè)bitmap的context,并把它設(shè)置成為當(dāng)前正在使用的context
    
    CGImageRef imgRef = image.CGImage;
    size_t width = CGImageGetWidth(imgRef);
    size_t height = CGImageGetHeight(imgRef);
    
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imgRef);

    BOOL hasAlpha = NO;
    if (alphaInfo == kCGImageAlphaPremultipliedLast ||
        alphaInfo == kCGImageAlphaPremultipliedFirst ||
        alphaInfo == kCGImageAlphaLast ||
        alphaInfo == kCGImageAlphaFirst) {
        hasAlpha = YES;
    }
    CGBitmapInfo info = kCGBitmapByteOrder32Host | (hasAlpha ? kCGImageAlphaPremultipliedFirst: kCGImageAlphaNoneSkipFirst);
    //創(chuàng)建一個(gè)bitmap的context
    CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, 8, 0, SharedCGColorSpaceGetDeviceRGB(), info);
    
    CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), imgRef);
    
    CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
    CGContextRelease(ctx);
    
    image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    
    return image;
}

4.使用CGDataProviderCopyData解壓,然后使用CGImageCreate重新創(chuàng)建解壓后的圖片镶蹋。

    CGImageRef imageRef = image.CGImage;
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
    size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
    size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
    size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
    CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);

    CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
    CFDataRef rawData = CGDataProviderCopyData(provider); //解壓
    provider = CGDataProviderCreateWithCFData(rawData);
    CFRelease(rawData);

    imageRef = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault);

    CGDataProviderRelease(provider);
    UIImage *newImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);

這是一個(gè)單純的解壓方法成艘,不像前面幾個(gè)方法都是繪制圖片,不過這個(gè)方法也有個(gè)瑕疵贺归,在使用過程中淆两,會(huì)出現(xiàn)內(nèi)存不斷增加的情況:進(jìn)入這個(gè)視圖控制器內(nèi)存增加一些,pop出去后再進(jìn)來又增加一些拂酣,不斷增長秋冰,可能從原本的100M內(nèi)存增加到800M,然后就會(huì)回落到100M婶熬,猜測可能是系統(tǒng)沒有釋放位圖數(shù)據(jù)緩存。

四、圖片的渲染

正常我們?cè)谑褂?code>imageView.image = image;的時(shí)候痰哨,在屏幕顯示的時(shí)候蚌铜,GPU需要繪制圖片,繪制就要渲染圖片饺谬,然后才能顯示出來捂刺。當(dāng)然我們這里說的渲染圖片是提前渲染和異步渲染。

1.把整張圖片繪制到CGContext中商蕴,然后獲取到context中的newImage并保存下來叠萍,原圖片就可以不用了,加載的時(shí)候需要加載newImage绪商,這里代碼就是在解壓欄目里我講的“繪制圖片”代碼苛谷。
這種方法多在tableView和collectionView上使用,異步繪制圖片然后回到主線程中加載格郁,可以使滑動(dòng)非常的流暢腹殿,這種操作主要是把CPU的同步解壓操作換成異步操作,并且把GPU的渲染換成了CPU的離屏渲染例书,具體方法在“解壓圖片”欄目中給出的2锣尉、3兩點(diǎn)。

2.把整個(gè)圖片只繪制到一個(gè)像素大小的bitmap Context中决采。

    CGImageRef cgImage = image.CGImage;
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    if (width == 0 || height == 0) return;
    
    size_t bitsPerComponent = 8;
    CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage);
    BOOL hasAlpha = NO;
    if (alphaInfo == kCGImageAlphaPremultipliedLast ||
        alphaInfo == kCGImageAlphaPremultipliedFirst ||
        alphaInfo == kCGImageAlphaLast ||
        alphaInfo == kCGImageAlphaFirst) {
        hasAlpha = YES;
    }
    CGBitmapInfo info = kCGBitmapByteOrder32Host | (hasAlpha ? kCGImageAlphaPremultipliedFirst: kCGImageAlphaNoneSkipFirst);
    
    CGContextRef context = CGBitmapContextCreate(NULL, 1, 1, bitsPerComponent, 0, SharedCGColorSpaceGetDeviceRGB(), info);
    //解壓渲染自沧,但是沒有優(yōu)化
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
    CGContextRelease(context);

這樣解壓整張圖片并提前渲染,好處是只繪制了一個(gè)像素,繪制沒有消耗任何時(shí)間拇厢。這樣沒有解壓后的圖片爱谁,只能加載原圖,解壓數(shù)據(jù)和渲染數(shù)據(jù)都在系統(tǒng)緩存中孝偎。
這種方法一般用于靜態(tài)頁面中加載圖片访敌。正因?yàn)橹患虞d原圖,導(dǎo)致原圖如果被釋放衣盾,解壓數(shù)據(jù)等在緩存中也會(huì)釋放寺旺,而且正因?yàn)橐蕾囉诰彺妫绻彺姹会尫攀凭觯敲催@個(gè)方法相當(dāng)于無效操作阻塑。

實(shí)際使用中,

  • 如果加載的是本地圖片徽龟,不大且重復(fù)使用叮姑,那最好還是使用+imageNamed:系統(tǒng)會(huì)做解壓緩存處理;
  • 如果加載本地大圖据悔,或者就一次使用的圖片传透,那么最好用+imageWithContentsOfFile:加載圖片;
  • 對(duì)于滑動(dòng)顯示大量圖片的頁面极颓,把整張圖片異步繪制到CGContext可能是更好的選擇朱盐。

這個(gè)demo是寫這邊文章時(shí)寫的AsynDisplayImage,有興趣可以看下菠隆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兵琳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子骇径,更是在濱河造成了極大的恐慌躯肌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件破衔,死亡現(xiàn)場離奇詭異清女,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)晰筛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門嫡丙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人读第,你說我怎么就攤上這事曙博。” “怎么了怜瞒?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵父泳,是天一觀的道長。 經(jīng)常有香客問我,道長尘吗,這世上最難降的妖魔是什么逝她? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮睬捶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘近刘。我一直安慰自己擒贸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布觉渴。 她就那樣靜靜地躺著介劫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪案淋。 梳的紋絲不亂的頭發(fā)上座韵,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音踢京,去河邊找鬼誉碴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛瓣距,可吹牛的內(nèi)容都是我干的黔帕。 我是一名探鬼主播,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼蹈丸,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼成黄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逻杖,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤奋岁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后荸百,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闻伶,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年管搪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了虾攻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡更鲁,死狀恐怖霎箍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情澡为,我是刑警寧澤漂坏,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響顶别,放射性物質(zhì)發(fā)生泄漏谷徙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一驯绎、第九天 我趴在偏房一處隱蔽的房頂上張望完慧。 院中可真熱鬧,春花似錦剩失、人聲如沸屈尼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脾歧。三九已至,卻和暖如春演熟,著一層夾襖步出監(jiān)牢的瞬間鞭执,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來泰國打工芒粹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兄纺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓是辕,卻偏偏與公主長得像囤热,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子获三,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

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