圖片渲染性能優(yōu)化

  • 誰吃掉我們的CPU: 方法CA::Render::create_image_from_provider
  • 圖片預(yù)解碼可以大幅提高ScrollView流暢度
  • 解碼方式:UIGraphic哩俭,CGContextWithAlpha翻具,CGContextWithoutAlpha弦撩,ImageIO
  • 單線程ImageIO性能最優(yōu)瞒瘸,多線程中ImageIO并不比其他方式占優(yōu)
  • 圖片為什么要解碼:一般下載或者從磁盤獲取的圖片是PNG或JPG,這是經(jīng)過編碼壓縮后的圖片數(shù)據(jù)殖演,不是位圖氧秘,要把它們渲染到屏幕前就需要進(jìn)行解碼轉(zhuǎn)成位圖數(shù)據(jù),而這個解碼操作比較耗時(shí)趴久。iOS默認(rèn)是在主線程解碼丸相,所以SDWebImage將這個過程放到子線程了。同時(shí)因?yàn)槲粓D體積很大彼棍,所以磁盤緩存不會直接緩存位圖數(shù)據(jù)灭忠,而是編碼壓縮后的PNG或JPG數(shù)據(jù)。

圖片加載的流程

  • 使用 +imageWithContentsOfFile: 方法從磁盤中加載一張圖片座硕,這個時(shí)候的圖片并沒有解壓縮弛作;
  • 將生成的 UIImage 賦值給 UIImageView ;
  • 一個隱式的 CATransaction 捕獲到了 UIImageView 圖層樹的變化华匾;
  • 在主線程的下一個 run loop 到來時(shí)映琳,Core Animation 提交了這個隱式的 transaction ,這個過程可能會對圖片進(jìn)行 copy 操作蜘拉,而受圖片是否字節(jié)對齊等因素的影響萨西,這個 copy 操作可能會涉及以下部分或全部步驟:
    1. 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;
    2. 將文件數(shù)據(jù)從磁盤讀到內(nèi)存中旭旭;
    3. 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式谎脯,這是一個非常耗時(shí)的 CPU 操作;
    4. 最后 Core Animation 使用未壓縮的位圖數(shù)據(jù)渲染 UIImageView 的圖層您机。

常用的解碼就是對圖片進(jìn)行重新繪制穿肄,得到一張新的解壓縮后的位圖。其中际看,用到的最核心的函數(shù)是 CGBitmapContextCreate :

/* Create a bitmap context. The context draws into a bitmap which is `width'
   pixels wide and `height' pixels high. The number of components for each
   pixel is specified by `space', which may also specify a destination color
   profile. The number of bits for each component of a pixel is specified by
   `bitsPerComponent'. The number of bytes per pixel is equal to
   `(bitsPerComponent * number of components + 7)/8'. Each row of the bitmap
   consists of `bytesPerRow' bytes, which must be at least `width * bytes
   per pixel' bytes; in addition, `bytesPerRow' must be an integer multiple
   of the number of bytes per pixel. `data', if non-NULL, points to a block
   of memory at least `bytesPerRow * height' bytes. If `data' is NULL, the
   data for context is allocated automatically and freed when the context is
   deallocated. `bitmapInfo' specifies whether the bitmap should contain an
   alpha channel and how it's to be generated, along with whether the
   components are floating-point or integer. */

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(__MAC_10_0, __IPHONE_2_0);
  • 位圖是一個像素?cái)?shù)組咸产,而像素格式則是用來描述每個像素的組成格式,它包括以下信息:
  1. Bits per component :一個像素中每個獨(dú)立的顏色分量使用的 bit 數(shù)仲闽。
  2. Bits per pixel :一個像素使用的總 bit 數(shù)脑溢。
  3. Bytes per row :位圖中的每一行使用的字節(jié)數(shù)。
  4. Color and Color Spaces:色彩空間。如0,0,1屑彻,顏色空間則是用來說明如何解析這些值的验庙,在RGB中代表藍(lán)色。
  5. Color Spaces and Bitmap Layout:像素格式是用來描述每個像素的組成格式的社牲,比如每個像素使用的總 bit 數(shù)粪薛。而要想確保 Quartz 能夠正確地解析這些 bit 所代表的含義,我們還需要提供位圖的布局信息 CGBitmapInfo :搏恤。包括3方面布局信息:alpha 的信息违寿、顏色分量是否為浮點(diǎn)數(shù)、像素格式的字節(jié)順序熟空。當(dāng)圖片不包含 alpha 的時(shí)候使用 kCGImageAlphaNoneSkipFirst 藤巢,否則使用 kCGImageAlphaPremultipliedFirst 。字節(jié)順序應(yīng)該使用 32 位的主機(jī)字節(jié)順序 kCGBitmapByteOrder32Host (不管當(dāng)前設(shè)備采用的是小端模式還是大端模式息罗,字節(jié)順序始終與其保持一致)掂咒。
  6. CGImageByteOrderInfo:像素字節(jié)順序。提供2方面信息:小端模式還是大端模式迈喉;數(shù)據(jù)以 16 位還是 32 位為單位绍刮。
  • 函數(shù)中參數(shù)的含義

  1. data :如果不為 NULL ,那么它應(yīng)該指向一塊大小至少為 bytesPerRow * height 字節(jié)的內(nèi)存弊添;如果 為 NULL 录淡,那么系統(tǒng)就會為我們自動分配和釋放所需的內(nèi)存捌木,所以一般指定 NULL 即可油坝;
  2. width 和 height :位圖的寬度和高度,分別賦值為圖片的像素寬度和像素高度即可刨裆;
  3. bitsPerComponent :像素的每個顏色分量使用的 bit 數(shù)澈圈,在 RGB 顏色空間下指定 8 即可;
  4. bytesPerRow :位圖的每一行使用的字節(jié)數(shù)帆啃,大小至少為 width * bytes per pixel 字節(jié)瞬女。有意思的是,當(dāng)我們指定 0 時(shí)努潘,系統(tǒng)不僅會為我們自動計(jì)算诽偷,而且還會進(jìn)行 cache line alignment 的優(yōu)化,更多信息可以查看 what is byte alignment (cache line alignment) for Core Animation? Why it matters?Why is my image’s Bytes per Row more than its Bytes per Pixel times its Width? 疯坤。
  5. space :就是我們前面提到的顏色空間报慕,一般使用 RGB 即可。
  6. bitmapInfo :就是我們前面提到的位圖的布局信息压怠。
  • SDWebImage中圖片解碼實(shí)現(xiàn)如下

+ (UIImage *)decodedImageWithImage:(UIImage *)image {
    // while downloading huge amount of images
    // autorelease the bitmap context
    // and all vars to help system to free memory
    // when there are memory warning.
    // on iOS7, do not forget to call
    // [[SDImageCache sharedImageCache] clearMemory];
    
    if (image == nil) { // Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
        return nil;
    }
    
    @autoreleasepool{
        // do not decode animated images
        if (image.images != nil) {
            return image;
        }
        
        CGImageRef imageRef = image.CGImage;
        
        CGImageAlphaInfo alpha = CGImageGetAlphaInfo(imageRef);
        BOOL anyAlpha = (alpha == kCGImageAlphaFirst ||
                         alpha == kCGImageAlphaLast ||
                         alpha == kCGImageAlphaPremultipliedFirst ||
                         alpha == kCGImageAlphaPremultipliedLast);
        if (anyAlpha) {
            return image;
        }
        
        // current
        CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(CGImageGetColorSpace(imageRef));
        CGColorSpaceRef colorspaceRef = CGImageGetColorSpace(imageRef);
        
        BOOL unsupportedColorSpace = (imageColorSpaceModel == kCGColorSpaceModelUnknown ||
                                      imageColorSpaceModel == kCGColorSpaceModelMonochrome ||
                                      imageColorSpaceModel == kCGColorSpaceModelCMYK ||
                                      imageColorSpaceModel == kCGColorSpaceModelIndexed);
        if (unsupportedColorSpace) {
            colorspaceRef = CGColorSpaceCreateDeviceRGB();
        }
        
        size_t width = CGImageGetWidth(imageRef);
        size_t height = CGImageGetHeight(imageRef);
        NSUInteger bytesPerPixel = 4;
        NSUInteger bytesPerRow = bytesPerPixel * width;
        NSUInteger bitsPerComponent = 8;


        // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
        // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
        // to create bitmap graphics contexts without alpha info.
        // 創(chuàng)建一個位圖上下文
        CGContextRef context = CGBitmapContextCreate(NULL,
                                                     width,
                                                     height,
                                                     bitsPerComponent,
                                                     bytesPerRow,
                                                     colorspaceRef,
                                                     kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
        
        // Draw the image into the context and retrieve the new bitmap image without alpha
        // 將原始位圖繪制到上下文中
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
        // 創(chuàng)建一張新的解壓縮后的位圖
        CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
        UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                         scale:image.scale
                                                   orientation:image.imageOrientation];
        
        if (unsupportedColorSpace) {
            CGColorSpaceRelease(colorspaceRef);
        }
        
        CGContextRelease(context);
        CGImageRelease(imageRefWithoutAlpha);
        
        return imageWithoutAlpha;
    }
}
  • YYKit眠冈、SDWebImage、FLAnimatedImage性能對比

  1. 參數(shù)設(shè)置


    image
  2. Github Demo

參考:iOS中的imageIO與image解碼

ImageIO

  • ImageIO對外開放的對象有CGImageSourceRef菌瘫、CGImageDestinationRef蜗顽,不對外開放的對象有CGImageMetadataRef布卡。CoreGraphics中經(jīng)常與ImageIO打交道的對象有CGImageRef和CGDataProvider。
  • 從CFDataRef到UIImage代碼如下:
NSString *resource = [[NSBundle mainBundle] pathForResource:@"xxx" ofType:@"png"];
NSData *data = [NSData dataWithContentsOfFile:resource options:0 error:nil];
   
CFDataRef dataRef = (__bridge CFDataRef)data;
// CGImageSourceRef跟讀取圖像數(shù)據(jù)有關(guān)
CGImageSourceRef source = CGImageSourceCreateWithData(dataRef, nil);
// CGImageSourceCreateImageAtIndex:調(diào)用了_cg_png_read_info和CGImageMetadataCreateMutable雇盖,在構(gòu)建CGImageRef時(shí)忿等,讀取了圖片的基礎(chǔ)數(shù)據(jù)和元數(shù)據(jù),基礎(chǔ)數(shù)據(jù)中包括Image的header chunk崔挖,比如png的IHDR(即文件頭數(shù)據(jù)塊)这弧。元數(shù)據(jù)是由CGImageMetadataRef來抽象的。并且沒有讀取圖片的其他數(shù)據(jù)虚汛,更沒有做解碼的動作匾浪。
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil);
UIImage *image = [UIImage imageWithCGImage:cgImage];

如果調(diào)用CGImageSourceCopyPropertiesAtIndex,CGImageSourceCopyPropertiesAtIndex的內(nèi)部函數(shù)調(diào)用了CGImageMetadataRef卷哩。說明:CGImageMetadataRef抽象出圖片中EXIF蛋辈、IPTC、XMP格式的元數(shù)據(jù)插入字段将谊,而若想獲得CGImageMetadataRef必須要通過CGImageSourceRef冷溶。

  • 將CGImageSourceRef改由CGDataProviderRef創(chuàng)建
CFDataRef dataRef = (__bridge CFDataRef)data;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);
CGImageSourceRef source = CGImageSourceCreateWithDataProvider(dataProvider, nil);
// 測試:由CGImageRef獲取CGDataProviderRef和由CGDataProviderRef創(chuàng)建CGImageRef
CGDataProviderRef newDataProvider = CGImageGetDataProvider(cgImage);

size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
CGColorSpaceRef space = CGImageGetColorSpace(cgImage);
size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(cgImage);
    
CGDataProviderRef newDataProvider = CGImageGetDataProvider(cgImage);
CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newDataProvider, NULL, false, kCGRenderingIntentDefault);
  • CGImageDestinationRef將圖片數(shù)據(jù)寫入目的地,并且負(fù)責(zé)做圖片編碼或者說圖片壓縮尊浓。
CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0);
CGImageDestinationRef destination = CGImageDestinationCreateWithData(buffer, kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(destination, cgImage, nil);
CGImageDestinationFinalize(destination);
  • ==結(jié)論==
  1. CGImageSourceRef抽象了對讀圖像數(shù)據(jù)的通道逞频,讀取圖像要通過它,它自己本身不讀取圖像的任何數(shù)據(jù)栋齿,在你調(diào)用CGImageSourceCopyPropertiesAtIndex的時(shí)候會才去讀取圖像元數(shù)據(jù)苗胀。
  2. CGImageMetadataRef抽象出圖片中EXIF、IPTC瓦堵、XMP格式的元數(shù)據(jù)基协,通過CGImageSourceRef獲取。
  3. CGImageRef抽象了圖像的基本數(shù)據(jù)和元數(shù)據(jù)菇用,創(chuàng)建的時(shí)候會通過CGImageSourceRef去讀取圖像的基礎(chǔ)數(shù)據(jù)和元數(shù)據(jù)澜驮,但沒有讀取圖像的其他數(shù)據(jù),沒有做圖片解碼的動作惋鸥。
  4. CGDataProviderRef沒有得出有用信息杂穷。
  5. CGImageDestinationRef抽象寫入圖像數(shù)據(jù)的通道,寫入圖像要通過它卦绣,在寫入圖片的時(shí)候還負(fù)責(zé)圖片的編碼耐量。

Image解碼

由上可知,從CFDataRef直到創(chuàng)建出UIImage迎卤,沒有調(diào)用過對圖像解碼的函數(shù)拴鸵,只讀取了一些圖像基礎(chǔ)數(shù)據(jù)和元數(shù)據(jù)。如果不手動設(shè)置Image只會等到在屏幕上渲染時(shí)再解碼。

  • 如果在畫布上渲染圖片劲藐,圖片一定是會被解碼的
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
    
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
    alphaInfo == kCGImageAlphaPremultipliedFirst ||
    alphaInfo == kCGImageAlphaLast ||
    alphaInfo == kCGImageAlphaFirst) {
    hasAlpha = YES;
}
    
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage); 
CGImageRef newImage = CGBitmapContextCreateImage(context);
  • Image解碼發(fā)生在CGDataProviderCopyData函數(shù)內(nèi)部調(diào)用ImageProviderCopyImageBlockSetCallback設(shè)置的callback或者copyImageBlock函數(shù)八堡,根據(jù)不同的圖片格式調(diào)用的不同的方法中。

Image的初始化

  • imageWithData:通過CGImageSourceRef訪問圖像數(shù)據(jù)聘芜,創(chuàng)建CGImageRef兄渺。
  • imageWithContentsOfFile:文件通過mmap到內(nèi)存然后通過CGImageSourceRef訪問圖像數(shù)據(jù),創(chuàng)建CGImageRef汰现。
  • imageNamed:先從Bundle里找到資源路徑挂谍,然后同樣也是將文件mmap到內(nèi)存,再通過CGImageSourceRef訪問圖像數(shù)據(jù)瞎饲,創(chuàng)建CGImageRef口叙。
    image

優(yōu)化

圖像

  • 使用網(wǎng)絡(luò)圖片時(shí)候并不能直接使用,需先從各種格式解碼到內(nèi)存后才能繪制嗅战,且解碼是一個相當(dāng)負(fù)責(zé)的過程妄田,相當(dāng)耗時(shí)。iOS推薦使用PNG圖片(一些很大的背景圖片可以考慮JPG或者JPEG)驮捍,是因?yàn)橥ǔNG圖片雖然加載到內(nèi)存會更慢(圖片通常會更大)疟呐,但是PNG的圖片的解碼算法更為簡單,耗時(shí)更少东且。耗時(shí)操作可以考慮異步線程來處理启具。
  • 如果想顯示圖片到比原始尺寸小的容器中,那么一次性在后臺線程重新繪制到正確的尺寸會比每次顯示的時(shí)候都做縮放會更有效珊泳。
  • 緩存本質(zhì)上就是用空間(內(nèi)存)來換性能:imageNamed會將所有的圖片緩存到內(nèi)存鲁冯,自帶緩存不會在對象銷毀直接清除,但是占用內(nèi)存較大旨椒;imageWithContentOfFile方法占用內(nèi)存較少晓褪,但沒有用自帶緩存,每次使用同一個圖片都需要重新加載解碼一遍综慎。所以如果圖片較小,并且頻繁使用的圖片,使用imageNamed來加載圖片(按鈕圖片/主頁圖片/占位圖);如果圖片較大勤庐,并且使用次數(shù)較少示惊,使用 imageWithContentOfFile:來加載(相冊/新特性頁面)。
  • imageNamed加載圖片之后會立刻進(jìn)行解碼愉镰,并由系統(tǒng)緩存圖片解碼后的數(shù)據(jù)(即刻占用內(nèi)存)米罚;imageWithContentsOfFile 加載圖片后,不會進(jìn)行解碼(直至渲染時(shí)才會占用內(nèi)存)丈探。
  • 分辨率超大的圖片處理:《iOS 核心動畫高級技巧》中有到 CATiledLayer 將大圖分解成小片然后將它們單獨(dú)按需載入录择。

圖層

  • 避免視圖中出現(xiàn)混合層,實(shí)驗(yàn)中opaque似乎不影響,alpha會影響隘竭。

  • UILabel如果顯示中文塘秦,就算是設(shè)置了backgroundColor仍然在查看混合圖層的時(shí)候,還是標(biāo)紅的动看。設(shè)置masksToBounds = YES即可解決(UILabel內(nèi)容是中文時(shí), label的實(shí)際渲染區(qū)域要大于label的size, 就是因?yàn)橥鈬幸蝗ν该? 才會有圖層混合)尊剔,Demo式例

  • 設(shè)置圓角masksToBounds就會導(dǎo)致離屏渲染菱皆,所以不要設(shè)置圓角须误,如果一定要使用圓角,可以使用UIGraphic去切圓角仇轻。

  • 圖片的使用盡量避免縮放京痢,一定要縮放同樣考慮使用UIGraphic去畫。

  • 在表格視圖中為了減少圖層數(shù)量可以直接篷店,啟用柵格化和離屏渲染历造。但是一定要使用instruments工具分析一下,是否有必要船庇,但是如果開啟柵格化就必須設(shè)置分辨率吭产,否則邊緣會有毛刺。

    //手動啟用離屏渲染
    self.layer.drawsAsynchronously = true // 該屬性對傳入-drawLayer:inContext:的CGContext進(jìn)行改動鸭轮,允許CGContext延緩繪制命令的執(zhí)行以至于不阻塞用戶交互臣淤。適用于圖層內(nèi)容需要反復(fù)重繪的情況。
    //手動啟用柵格化
    self.layer.shouldRasterize = true
    //啟用柵格化必須設(shè)置設(shè)備的分辨率窃爷,否則可能會出現(xiàn)毛刺
    self.layer.rasterizationScale = UIScreen.main.scale
    
  • 包含文本的視圖UILabel使用的時(shí)候邑蒋,盡量避免修改修改frame,修改frame會導(dǎo)致文本重繪按厘。

  • 如果圖層不會被頻繁重繪医吊,可以對離屏渲染的圖層使用柵格化,作為一種優(yōu)化方式逮京。

  • 表格控件不要動態(tài)創(chuàng)建控件卿堂,創(chuàng)建豐富的控件,在顯示的時(shí)候根據(jù)數(shù)據(jù)隱藏或者顯示懒棉。

補(bǔ)充:

光柵化:雙刃劍
  1. TableViewCell的重繪是很頻繁的(因?yàn)镃ell的復(fù)用),如果Cell的內(nèi)容不斷變化,則Cell需要不斷重繪,如果此時(shí)設(shè)置了cell.layer可光柵化草描。則會造成大量的offscreen渲染,降低圖形性能。
  2. 當(dāng)然,合理利用的話,是能夠得到不少性能的提高的,因?yàn)槭褂胹houldRasterize后layer會緩存為Bitmap位圖,對一些添加了shawdow等效果的耗費(fèi)資源較多的靜態(tài)內(nèi)容進(jìn)行緩存,能夠得到性能的提升策严。
離屏渲染
  1. 指的是在圖像在繪制到當(dāng)前屏幕前,需要先進(jìn)行一次渲染,之后才繪制到當(dāng)前屏幕穗慕。
  2. 離屏渲染會消耗大量資源:GPU需要另外alloc一塊內(nèi)存(CPU)來進(jìn)行渲染(就是使用CPU進(jìn)行渲染),渲染完畢后在繪制到當(dāng)前屏幕,而且對于顯卡來說,onscreen到offscreen的上下文環(huán)境切換是非常昂貴的(這個過程的消耗會比較昂貴,涉及到OpenGL的pipeline跟barrier)妻导。
  3. 使用CPU造成離屏渲染操作包括:drawRect方法(如沒有自定義繪制的任務(wù)就不要在子類中寫一個空的drawRect方法逛绵,因?yàn)橹灰獙?shí)現(xiàn)了該方法怀各,就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值术浪,造成資源浪費(fèi))瓢对;使用Core Graphics。

此過程如下:首先分配一塊內(nèi)存添吗,然后進(jìn)行渲染操作生成一份bitmap位圖沥曹,整個渲染過程會在你的應(yīng)用中同步的進(jìn)行,接著再將位圖打包發(fā)送到iOS里一個單獨(dú)的進(jìn)程--render server碟联,理想情況下妓美,render server將內(nèi)容交給GPU直接顯示到屏幕上。

  1. 使用GPU造成離屏渲染操作包括:設(shè)置cornerRadius, masks, shadows,edge antialiasing(view的縮放的時(shí)候鲤孵,layer.border.width隨著view的放大壶栋,會出現(xiàn)鋸齒化的問題),layer.edgeAntialiasingMask等普监;開啟光柵化:設(shè)置layer.shouldRasterize = YES贵试;
  2. 解決辦法:
使用Instruments
  1. 混合層檢查:Color Blended layers。標(biāo)示混合的圖層會為紅色,不透明的圖層為綠色凯正,通常我們希望綠色的區(qū)域越多越好毙玻。
  2. 開啟光柵化是否命中緩存:Color Hits Green and Misses Red。設(shè)置viewlayer的shouldRasterize為YES廊散,那些成功被緩存的layer會標(biāo)注為綠色桑滩,反之為紅色。
  3. Color copied images:表示那些被Core Animation拷貝的圖片允睹。這主要是因?yàn)樵搱D片的色彩格式不能被GPU直接處理运准,需要在CPU這邊做轉(zhuǎn)換,假如在主線層做這個操作對性能會有一定的影響缭受。
  4. Color misaligned images:被縮放的圖片會被標(biāo)記為黃色,像素不對齊則會標(biāo)注為紫色胁澳。
  5. 離屏渲染:Color offscreen-rendered yellow。黃色越多則離屏渲染的越多米者。

UIView的alpha韭畸、hidden和opaque屬性之間的關(guān)系和區(qū)別
注意幾點(diǎn):

  • 設(shè)置backgroundColor的alpha值影響當(dāng)前UIView的背景,不會影響subview塘雳。
  • opaque默認(rèn)為YES陆盘,如果alpha < 1,那么應(yīng)該設(shè)置opaque設(shè)置為NO败明;但是如果view對應(yīng)的alpha < 1,opaque設(shè)置為YES太防,產(chǎn)生的后果是不可預(yù)料的妻顶。

繪圖

image
  • Core Graphics Framework是一套基于C的API框架酸员,使用了Quartz作為繪圖引擎。它提供了低級別讳嘱、輕量級幔嗦、高保真度的2D渲染。該框架可以用于基于路徑的 繪圖沥潭、變換邀泉、顏色管理、脫屏渲染钝鸽,模板汇恤、漸變、遮蔽拔恰、圖像數(shù)據(jù)管理因谎、圖像的創(chuàng)建、遮罩以及PDF文檔的創(chuàng)建颜懊、顯示和分析财岔。

  • iOS支持兩套圖形API族:Core Graphics/QuartZ 2D 和OpenGL ES/OpenGL。

    1. Core Graphics是一個繪圖專用的API族河爹,它經(jīng)常被稱為QuartZ或QuartZ 2D匠璧。Core Graphics是iOS上所有繪圖功能的基石,包括UIKit咸这。QuartZ 2D是蘋果公司開發(fā)的一套API夷恍,它是Core Graphics Framework的一部分。
    2. OpenGL ES是跨平臺的圖形API炊苫,屬于OpenGL的一個簡化版本
  • 注意:OpenGL ES是應(yīng)用程序編程接口裁厅,該接口描述了方法、結(jié)構(gòu)侨艾、函數(shù)應(yīng)具有的行為以及應(yīng)該如何被使用的語義执虹。也就是說它只定義了一套規(guī)范,具體的實(shí)現(xiàn)由設(shè)備制造商根據(jù) 規(guī)范去做唠梨。因?yàn)橹圃?商可以自由的實(shí)現(xiàn)Open
    GL ES袋励,所以不同系統(tǒng)實(shí)現(xiàn)的OpenGL ES也存在著巨大的性能差異。

  • Core Graphics API所有的操作都在一個上下文中進(jìn)行当叭。所以在繪圖之前需要獲取該上下文并傳入執(zhí)行渲染的函數(shù)中茬故。如果你正在渲染一副在內(nèi)存中的圖片,此時(shí)就需要傳入圖片 所屬的上下文蚁鳖。獲得一個圖形上下文是我們完成繪圖任務(wù)的第一步磺芭,你可以將圖形上下文理解為一塊畫布。如果你沒有得到這塊畫布醉箕,那么你就無法完成任何繪圖操 作钾腺。

  • 兩種最為常用的獲取上下文方法:

    1. 調(diào)用UIGraphicsBeginImageContextWithOptions函數(shù)徙垫;
    2. 利用cocoa為你生成的圖形上下文。當(dāng)你子類化了一個UIView并實(shí)現(xiàn)了自己的drawRect:方法后放棒,一旦drawRect:方法被調(diào)用姻报,Cocoa就會為你創(chuàng)建一個圖形上下文,此時(shí)你對圖形上下文的所有繪圖操作都會顯示在UIView上间螟。
    3. drawRect: inContext:吴旋。
  • 使用UIKit,你只能在當(dāng)前上下文中繪圖厢破,所以如果你當(dāng)前處于 UIGraphicsBeginImageContextWithOptions函數(shù)或drawRect:方法中荣瑟,你就可以直接使用UIKit提供的方法 進(jìn)行繪圖。如果你持有一個context:參數(shù)溉奕,那么使用UIKit提供的方法之前褂傀,必須將該上下文參數(shù)轉(zhuǎn)化為當(dāng)前上下文。幸運(yùn)的是加勤,調(diào)用 UIGraphicsPushContext 函數(shù)可以方便的將context:參數(shù)轉(zhuǎn)化為當(dāng)前上下文仙辟,記住最后別忘了調(diào)用UIGraphicsPopContext函數(shù)恢復(fù)上下文環(huán)境。

  • 如果你當(dāng)前處于 UIGraphicsBeginImageContextWithOptions函數(shù)或drawRect:方法中鳄梅,并沒有引用一個上下文叠国。為了使用 Core Graphics,你可以調(diào)用UIGraphicsGetCurrentContext函數(shù)獲得當(dāng)前的圖形上下文戴尸。

  • 兩大繪圖框架的支持以及三種獲得圖形上下文的方法(drawRect:粟焊、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions),那么就有6種繪圖的形式孙蒙。
    1项棠、 在UIView的子類方法drawRect:中繪制一個藍(lán)色圓,使用UIKit在Cocoa為我們提供的當(dāng)前上下文中完成繪圖任務(wù)挎峦。

- (void)drawRect:(CGRect)rect {  
    UIBezierPath *p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
    [[UIColor blueColor] setFill];  
    [p fill];  
}

2香追、 使用Core Graphics實(shí)現(xiàn)繪制藍(lán)色圓。

- (void)drawRect:(CGRect)rect {  
    CGContextRef con = UIGraphicsGetCurrentContext();
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
    CGContextFillPath(con);  
}

3坦胶、 我將在UIView子類的drawLayer:inContext:方法中實(shí)現(xiàn)繪圖任務(wù)透典。 drawLayer:inContext:方法是一個繪制圖層內(nèi)容的代理方法。為了能夠調(diào)用drawLayer:inContext:方法顿苇,我們需要設(shè)定 圖層的代理對象峭咒。但要注意,不應(yīng)該將UIView對象設(shè)置為顯示層的委托對象纪岁,這是因?yàn)閁IView對象已經(jīng)是隱式層的代理對象凑队,再將它設(shè)置為另一個層的 委托對象就會出問題。輕量級的做法是:編寫負(fù)責(zé)繪圖形的代理類幔翰。在MyView.h文件中聲明如下代碼:

@interface MyLayerDelegate : NSObject  
@end

@implementation MyLayerDelegate  
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx {
    UIGraphicsPushContext(ctx);    
    UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
    [[UIColor blueColor] setFill];
    [p fill];    
    UIGraphicsPopContext();  
}  
@end

@interface MyView () {
    MyLayerDelegate *_layerDeleagete;  
}  
@end

// 使用
MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];  
CALayer *myLayer = [CALayer layer];  
_layerDelegate = [[MyLayerDelegate alloc] init];  
myLayer.delegate = _layerDelegate;  
[myView.layer addSublayer:myLayer];  
[myView setNeedsDisplay]; // 調(diào)用此方法顽决,drawLayer: inContext:方法才會被調(diào)用

4短条、 使用Core Graphics在drawLayer:inContext:方法中實(shí)現(xiàn)同樣操作导匣,代碼如下:

- (void)drawLayer:(CALayer*)lay inContext:(CGContextRef)con { 
    CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
    CGContextFillPath(con);  
}

5才菠、 使用UIKit實(shí)現(xiàn):

// 第一個參數(shù)表示所要創(chuàng)建的圖片的尺寸;第二個參 數(shù)用來指定所生成圖片的背景是否為不透明贡定,如上我們使用YES而不是NO赋访,則我們得到的圖片背景將會是黑色,顯然這不是我想要的缓待;第三個參數(shù)指定生成圖片 的縮放因子蚓耽,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據(jù)屏幕的分辨率而變化旋炒,所以我們得到的圖 片不管是在單分辨率還是視網(wǎng)膜屏上看起來都會很好步悠。
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];  
[[UIColor blueColor] setFill];  
[p fill];  
UIImage *im = UIGraphicsGetImageFromCurrentImageContext(); 
UIGraphicsEndImageContext();

6、 使用Core Graphics實(shí)現(xiàn):

UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);  
UIImage *im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

參考:https://my.oschina.net/u/248165/blog/224309


UIGraphic解碼瘫镇?不知道是否是下面的操作

- (UIImage *)imageWithAspectScaleSize:(CGSize)newSize {
    // 創(chuàng)建一個基于位圖的上下文
    UIGraphicsBeginImageContext(newSize);
    CGFloat widthScale = newSize.width / self.size.width;
    CGFloat heightScale = newSize.height / self.size.height;
    CGFloat scale = MIN(widthScale, heightScale);
    CGSize drawSize = CGSizeMake(self.size.width * scale, self.size.width * scale);
    [self drawInRect:CGRectMake((newSize.width - drawSize.width) / 2, (newSize.height - drawSize.height) / 2, drawSize.width, drawSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsPopContext();
    
    return newImage;
}
  • UIGraphicsBeginImageContext 與CGBitmapContextCreate 的區(qū)別
    UIGraphicsBeginImageContext is a UIKit wrapper that sits on top of CGBitmapContextCreate and reduces its functionality鼎兽。也就是說UIGraphicsBeginImageContext高級一點(diǎn),CGBitmapContextCreate底層一點(diǎn)铣除,功能靈活點(diǎn)

CGContextWithAlpha解碼谚咬?


CGContextWithoutAlpha解碼?


ImageIO解碼

  • 步驟

    1. CGImageSourceCreateWithData(data) 創(chuàng)建 ImageSource尚粘。
    2. CGImageSourceCreateImageAtIndex(source) 創(chuàng)建一個未解碼的 CGImage择卦。
    3. CGImageGetDataProvider(image) 獲取這個圖片的數(shù)據(jù)源。
    4. CGDataProviderCopyData(provider) 從數(shù)據(jù)源獲取直接解碼的數(shù)據(jù)郎嫁。
      ImageIO 解碼發(fā)生在最后一步秉继,這樣獲得的數(shù)據(jù)是沒有經(jīng)過顏色類型轉(zhuǎn)換的原生數(shù)據(jù)(比如灰度圖像)。
  • YYKit中方法

CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL);
CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)});
CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES);
CFRelease(decoded);
CFRelease(image);
CFRelease(source);
CGImageRef YYCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
    if (!imageRef) return NULL;
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    if (width == 0 || height == 0) return NULL;
    
    if (decodeForDisplay) { //decode with redraw (may lose some precision)
        // 解碼和重繪:可能會丟失一些精度
        CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
        BOOL hasAlpha = NO;
        if (alphaInfo == kCGImageAlphaPremultipliedLast ||
            alphaInfo == kCGImageAlphaPremultipliedFirst ||
            alphaInfo == kCGImageAlphaLast ||
            alphaInfo == kCGImageAlphaFirst) {
            hasAlpha = YES;
        }
        // BGRA8888 (premultiplied) or BGRX8888
        // same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
        CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
        bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
        CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, YYCGColorSpaceGetDeviceRGB(), bitmapInfo);
        if (!context) return NULL;
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
        CGImageRef newImage = CGBitmapContextCreateImage(context);
        CFRelease(context);
        return newImage;
        
    } else {
        // 直接解碼
        CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
        size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
        size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
        size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
        CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
        if (bytesPerRow == 0 || width == 0 || height == 0) return NULL;
        
        CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
        if (!dataProvider) return NULL;
        //  這里進(jìn)行的解碼:CGDataProviderCopyData 內(nèi)部會調(diào)用 ImageProviderCopyImageBlockSetCallback 和 copyImageBlock泽铛,得到的 CFDataRef 是解碼過的像素?cái)?shù)組尚辑。
        CFDataRef data = CGDataProviderCopyData(dataProvider);
        if (!data) return NULL;
        
        CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data);
        CFRelease(data);
        if (!newProvider) return NULL;
        
        CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault);
        CFRelease(newProvider);
        return newImage;
    }
}

ImageIO編碼

  • YYKit中方法
NSString *fileName = [NSString stringWithFormat:@"%@%@_imageio",imageName, imageSize];
NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:@"png"];
NSData *data = filePath ? [NSData dataWithContentsOfFile:filePath] : nil;
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFTypeRef)data, NULL);
CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, (CFDictionaryRef)@{(id)kCGImageSourceShouldCache:@(NO)});
CGImageRef decoded = YYCGImageCreateDecodedCopy(image, YES);

CFMutableDataRef outData = NULL;
long length = 0;

if (outData) CFRelease(outData);
outData = CFDataCreateMutable(CFAllocatorGetDefault(), 0);
CGImageDestinationRef dest = CGImageDestinationCreateWithData(outData, (CFStringRef)uti, 1, NULL);
NSDictionary *options = @{(id)kCGImageDestinationLossyCompressionQuality : quality };
CGImageDestinationAddImage(dest, decoded, (CFDictionaryRef)options);
CGImageDestinationFinalize(dest);
length = CFDataGetLength(outData);
CFRelease(dest);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市厚宰,隨后出現(xiàn)的幾起案子腌巾,更是在濱河造成了極大的恐慌,老刑警劉巖铲觉,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澈蝙,死亡現(xiàn)場離奇詭異,居然都是意外死亡撵幽,警方通過查閱死者的電腦和手機(jī)灯荧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門泡态,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甥材,“玉大人,你說我怎么就攤上這事〔黾牵” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵兼犯,是天一觀的道長乳规。 經(jīng)常有香客問我,道長擦秽,這世上最難降的妖魔是什么码荔? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮感挥,結(jié)果婚禮上缩搅,老公的妹妹穿的比我還像新娘。我一直安慰自己触幼,他們只是感情好硼瓣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著置谦,像睡著了一般堂鲤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霉祸,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天筑累,我揣著相機(jī)與錄音,去河邊找鬼丝蹭。 笑死慢宗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奔穿。 我是一名探鬼主播镜沽,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贱田!你這毒婦竟也來了缅茉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤男摧,失蹤者是張志新(化名)和其女友劉穎蔬墩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耗拓,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拇颅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乔询。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片樟插。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出黄锤,到底是詐尸還是另有隱情搪缨,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布鸵熟,位于F島的核電站副编,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏旅赢。R本人自食惡果不足惜齿桃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望煮盼。 院中可真熱鬧,春花似錦带污、人聲如沸僵控。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽报破。三九已至,卻和暖如春千绪,著一層夾襖步出監(jiān)牢的瞬間充易,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工荸型, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盹靴,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓瑞妇,卻偏偏與公主長得像稿静,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辕狰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

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

  • 繪制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一個像素是如何繪制到屏幕上去的改备?有很多...
    阿貍旅途T恤閱讀 1,625評論 0 7
  • 卷首語 歡迎來到 objc.io 的第三期! 這一期都是關(guān)于視圖層的蔓倍。當(dāng)然視圖層有很多方面悬钳,我們需要把它們縮小到幾...
    評評分分閱讀 1,757評論 0 18
  • 一默勾、Quartz 2D 中的數(shù)據(jù)管理 數(shù)據(jù)管理是每個圖形應(yīng)用程序必須執(zhí)行的任務(wù)。在 Quartz2D 中數(shù)據(jù)管理涉...
    尋形覓影閱讀 811評論 0 2
  • 人的一生中會有很多際遇倒堕,會碰到很多人灾测,會發(fā)生很多故事,其中不乏會有一些不好的事情,人生不如意事十之八九媳搪,要常想一二...
    小毛猴閱讀 1,587評論 2 3
  • 一铭段、財(cái)務(wù)分析報(bào)告的分類 財(cái)務(wù)分析報(bào)告從編寫的時(shí)間來劃分,可分為兩種:一是定期分析報(bào)告秦爆,二是非定期分析報(bào)告序愚。定期分析...
    芳菲語閱讀 640評論 0 7