圖片渲染性能優化

  • 誰吃掉我們的CPU: 方法CA::Render::create_image_from_provider
  • 圖片預解碼可以大幅提高ScrollView流暢度
  • 解碼方式:UIGraphic,CGContextWithAlpha,CGContextWithoutAlpha,ImageIO
  • 單線程ImageIO性能最優,多線程中ImageIO并不比其他方式占優
  • 圖片為什么要解碼:一般下載或者從磁盤獲取的圖片是PNG或JPG,這是經過編碼壓縮后的圖片數據,不是位圖,要把它們渲染到屏幕前就需要進行解碼轉成位圖數據,而這個解碼操作比較耗時。iOS默認是在主線程解碼,所以SDWebImage將這個過程放到子線程了。同時因為位圖體積很大,所以磁盤緩存不會直接緩存位圖數據,而是編碼壓縮后的PNG或JPG數據。

圖片加載的流程

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

常用的解碼就是對圖片進行重新繪制,得到一張新的解壓縮后的位圖。其中,用到的最核心的函數是 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);
  • 位圖是一個像素數組,而像素格式則是用來描述每個像素的組成格式,它包括以下信息:
  1. Bits per component :一個像素中每個獨立的顏色分量使用的 bit 數。
  2. Bits per pixel :一個像素使用的總 bit 數。
  3. Bytes per row :位圖中的每一行使用的字節數。
  4. Color and Color Spaces:色彩空間。如0,0,1,顏色空間則是用來說明如何解析這些值的,在RGB中代表藍色。
  5. Color Spaces and Bitmap Layout:像素格式是用來描述每個像素的組成格式的,比如每個像素使用的總 bit 數。而要想確保 Quartz 能夠正確地解析這些 bit 所代表的含義,我們還需要提供位圖的布局信息 CGBitmapInfo :。包括3方面布局信息:alpha 的信息、顏色分量是否為浮點數、像素格式的字節順序。當圖片不包含 alpha 的時候使用 kCGImageAlphaNoneSkipFirst ,否則使用 kCGImageAlphaPremultipliedFirst 。字節順序應該使用 32 位的主機字節順序 kCGBitmapByteOrder32Host (不管當前設備采用的是小端模式還是大端模式,字節順序始終與其保持一致)。
  6. CGImageByteOrderInfo:像素字節順序。提供2方面信息:小端模式還是大端模式;數據以 16 位還是 32 位為單位。
  • 函數中參數的含義

  1. data :如果不為 NULL ,那么它應該指向一塊大小至少為 bytesPerRow * height 字節的內存;如果 為 NULL ,那么系統就會為我們自動分配和釋放所需的內存,所以一般指定 NULL 即可;
  2. width 和 height :位圖的寬度和高度,分別賦值為圖片的像素寬度和像素高度即可;
  3. bitsPerComponent :像素的每個顏色分量使用的 bit 數,在 RGB 顏色空間下指定 8 即可;
  4. bytesPerRow :位圖的每一行使用的字節數,大小至少為 width * bytes per pixel 字節。有意思的是,當我們指定 0 時,系統不僅會為我們自動計算,而且還會進行 cache line alignment 的優化,更多信息可以查看 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中圖片解碼實現如下

+ (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.
        // 創建一個位圖上下文
        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);
        // 創建一張新的解壓縮后的位圖
        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. 參數設置


    image
  2. Github Demo

參考:iOS中的imageIO與image解碼

ImageIO

  • ImageIO對外開放的對象有CGImageSourceRef、CGImageDestinationRef,不對外開放的對象有CGImageMetadataRef。CoreGraphics中經常與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跟讀取圖像數據有關
CGImageSourceRef source = CGImageSourceCreateWithData(dataRef, nil);
// CGImageSourceCreateImageAtIndex:調用了_cg_png_read_info和CGImageMetadataCreateMutable,在構建CGImageRef時,讀取了圖片的基礎數據和元數據,基礎數據中包括Image的header chunk,比如png的IHDR(即文件頭數據塊)。元數據是由CGImageMetadataRef來抽象的。并且沒有讀取圖片的其他數據,更沒有做解碼的動作。
CGImageRef cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil);
UIImage *image = [UIImage imageWithCGImage:cgImage];

如果調用CGImageSourceCopyPropertiesAtIndex,CGImageSourceCopyPropertiesAtIndex的內部函數調用了CGImageMetadataRef。說明:CGImageMetadataRef抽象出圖片中EXIF、IPTC、XMP格式的元數據插入字段,而若想獲得CGImageMetadataRef必須要通過CGImageSourceRef。

  • 將CGImageSourceRef改由CGDataProviderRef創建
CFDataRef dataRef = (__bridge CFDataRef)data;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(dataRef);
CGImageSourceRef source = CGImageSourceCreateWithDataProvider(dataProvider, nil);
// 測試:由CGImageRef獲取CGDataProviderRef和由CGDataProviderRef創建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將圖片數據寫入目的地,并且負責做圖片編碼或者說圖片壓縮。
CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0);
CGImageDestinationRef destination = CGImageDestinationCreateWithData(buffer, kUTTypePNG, 1, NULL);
CGImageDestinationAddImage(destination, cgImage, nil);
CGImageDestinationFinalize(destination);
  • ==結論==
  1. CGImageSourceRef抽象了對讀圖像數據的通道,讀取圖像要通過它,它自己本身不讀取圖像的任何數據,在你調用CGImageSourceCopyPropertiesAtIndex的時候會才去讀取圖像元數據。
  2. CGImageMetadataRef抽象出圖片中EXIF、IPTC、XMP格式的元數據,通過CGImageSourceRef獲取。
  3. CGImageRef抽象了圖像的基本數據和元數據,創建的時候會通過CGImageSourceRef去讀取圖像的基礎數據和元數據,但沒有讀取圖像的其他數據,沒有做圖片解碼的動作。
  4. CGDataProviderRef沒有得出有用信息。
  5. CGImageDestinationRef抽象寫入圖像數據的通道,寫入圖像要通過它,在寫入圖片的時候還負責圖片的編碼。

Image解碼

由上可知,從CFDataRef直到創建出UIImage,沒有調用過對圖像解碼的函數,只讀取了一些圖像基礎數據和元數據。如果不手動設置Image只會等到在屏幕上渲染時再解碼。

  • 如果在畫布上渲染圖片,圖片一定是會被解碼的
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解碼發生在CGDataProviderCopyData函數內部調用ImageProviderCopyImageBlockSetCallback設置的callback或者copyImageBlock函數,根據不同的圖片格式調用的不同的方法中。

Image的初始化

  • imageWithData:通過CGImageSourceRef訪問圖像數據,創建CGImageRef。
  • imageWithContentsOfFile:文件通過mmap到內存然后通過CGImageSourceRef訪問圖像數據,創建CGImageRef。
  • imageNamed:先從Bundle里找到資源路徑,然后同樣也是將文件mmap到內存,再通過CGImageSourceRef訪問圖像數據,創建CGImageRef。
    image

優化

圖像

  • 使用網絡圖片時候并不能直接使用,需先從各種格式解碼到內存后才能繪制,且解碼是一個相當負責的過程,相當耗時。iOS推薦使用PNG圖片(一些很大的背景圖片可以考慮JPG或者JPEG),是因為通常PNG圖片雖然加載到內存會更慢(圖片通常會更大),但是PNG的圖片的解碼算法更為簡單,耗時更少。耗時操作可以考慮異步線程來處理。
  • 如果想顯示圖片到比原始尺寸小的容器中,那么一次性在后臺線程重新繪制到正確的尺寸會比每次顯示的時候都做縮放會更有效。
  • 緩存本質上就是用空間(內存)來換性能:imageNamed會將所有的圖片緩存到內存,自帶緩存不會在對象銷毀直接清除,但是占用內存較大;imageWithContentOfFile方法占用內存較少,但沒有用自帶緩存,每次使用同一個圖片都需要重新加載解碼一遍。所以如果圖片較小,并且頻繁使用的圖片,使用imageNamed來加載圖片(按鈕圖片/主頁圖片/占位圖);如果圖片較大,并且使用次數較少,使用 imageWithContentOfFile:來加載(相冊/新特性頁面)。
  • imageNamed加載圖片之后會立刻進行解碼,并由系統緩存圖片解碼后的數據(即刻占用內存);imageWithContentsOfFile 加載圖片后,不會進行解碼(直至渲染時才會占用內存)。
  • 分辨率超大的圖片處理:《iOS 核心動畫高級技巧》中有到 CATiledLayer 將大圖分解成小片然后將它們單獨按需載入。

圖層

  • 避免視圖中出現混合層,實驗中opaque似乎不影響,alpha會影響。

  • UILabel如果顯示中文,就算是設置了backgroundColor仍然在查看混合圖層的時候,還是標紅的。設置masksToBounds = YES即可解決(UILabel內容是中文時, label的實際渲染區域要大于label的size, 就是因為外圍有一圈透明, 才會有圖層混合),Demo式例

  • 設置圓角masksToBounds就會導致離屏渲染,所以不要設置圓角,如果一定要使用圓角,可以使用UIGraphic去切圓角。

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

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

    //手動啟用離屏渲染
    self.layer.drawsAsynchronously = true // 該屬性對傳入-drawLayer:inContext:的CGContext進行改動,允許CGContext延緩繪制命令的執行以至于不阻塞用戶交互。適用于圖層內容需要反復重繪的情況。
    //手動啟用柵格化
    self.layer.shouldRasterize = true
    //啟用柵格化必須設置設備的分辨率,否則可能會出現毛刺
    self.layer.rasterizationScale = UIScreen.main.scale
    
  • 包含文本的視圖UILabel使用的時候,盡量避免修改修改frame,修改frame會導致文本重繪。

  • 如果圖層不會被頻繁重繪,可以對離屏渲染的圖層使用柵格化,作為一種優化方式。

  • 表格控件不要動態創建控件,創建豐富的控件,在顯示的時候根據數據隱藏或者顯示。

補充:

光柵化:雙刃劍
  1. TableViewCell的重繪是很頻繁的(因為Cell的復用),如果Cell的內容不斷變化,則Cell需要不斷重繪,如果此時設置了cell.layer可光柵化。則會造成大量的offscreen渲染,降低圖形性能。
  2. 當然,合理利用的話,是能夠得到不少性能的提高的,因為使用shouldRasterize后layer會緩存為Bitmap位圖,對一些添加了shawdow等效果的耗費資源較多的靜態內容進行緩存,能夠得到性能的提升。
離屏渲染
  1. 指的是在圖像在繪制到當前屏幕前,需要先進行一次渲染,之后才繪制到當前屏幕。
  2. 離屏渲染會消耗大量資源:GPU需要另外alloc一塊內存(CPU)來進行渲染(就是使用CPU進行渲染),渲染完畢后在繪制到當前屏幕,而且對于顯卡來說,onscreen到offscreen的上下文環境切換是非常昂貴的(這個過程的消耗會比較昂貴,涉及到OpenGL的pipeline跟barrier)。
  3. 使用CPU造成離屏渲染操作包括:drawRect方法(如沒有自定義繪制的任務就不要在子類中寫一個空的drawRect方法,因為只要實現了該方法,就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值,造成資源浪費);使用Core Graphics。

此過程如下:首先分配一塊內存,然后進行渲染操作生成一份bitmap位圖,整個渲染過程會在你的應用中同步的進行,接著再將位圖打包發送到iOS里一個單獨的進程--render server,理想情況下,render server將內容交給GPU直接顯示到屏幕上。

  1. 使用GPU造成離屏渲染操作包括:設置cornerRadius, masks, shadows,edge antialiasing(view的縮放的時候,layer.border.width隨著view的放大,會出現鋸齒化的問題),layer.edgeAntialiasingMask等;開啟光柵化:設置layer.shouldRasterize = YES;
  2. 解決辦法:
使用Instruments
  1. 混合層檢查:Color Blended layers。標示混合的圖層會為紅色,不透明的圖層為綠色,通常我們希望綠色的區域越多越好。
  2. 開啟光柵化是否命中緩存:Color Hits Green and Misses Red。設置viewlayer的shouldRasterize為YES,那些成功被緩存的layer會標注為綠色,反之為紅色。
  3. Color copied images:表示那些被Core Animation拷貝的圖片。這主要是因為該圖片的色彩格式不能被GPU直接處理,需要在CPU這邊做轉換,假如在主線層做這個操作對性能會有一定的影響。
  4. Color misaligned images:被縮放的圖片會被標記為黃色,像素不對齊則會標注為紫色。
  5. 離屏渲染:Color offscreen-rendered yellow。黃色越多則離屏渲染的越多。

UIView的alpha、hidden和opaque屬性之間的關系和區別
注意幾點:

  • 設置backgroundColor的alpha值影響當前UIView的背景,不會影響subview。
  • opaque默認為YES,如果alpha < 1,那么應該設置opaque設置為NO;但是如果view對應的alpha < 1,opaque設置為YES,產生的后果是不可預料的。

繪圖

image
  • Core Graphics Framework是一套基于C的API框架,使用了Quartz作為繪圖引擎。它提供了低級別、輕量級、高保真度的2D渲染。該框架可以用于基于路徑的 繪圖、變換、顏色管理、脫屏渲染,模板、漸變、遮蔽、圖像數據管理、圖像的創建、遮罩以及PDF文檔的創建、顯示和分析。

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

    1. Core Graphics是一個繪圖專用的API族,它經常被稱為QuartZ或QuartZ 2D。Core Graphics是iOS上所有繪圖功能的基石,包括UIKit。QuartZ 2D是蘋果公司開發的一套API,它是Core Graphics Framework的一部分。
    2. OpenGL ES是跨平臺的圖形API,屬于OpenGL的一個簡化版本
  • 注意:OpenGL ES是應用程序編程接口,該接口描述了方法、結構、函數應具有的行為以及應該如何被使用的語義。也就是說它只定義了一套規范,具體的實現由設備制造商根據 規范去做。因為制造 商可以自由的實現Open
    GL ES,所以不同系統實現的OpenGL ES也存在著巨大的性能差異。

  • Core Graphics API所有的操作都在一個上下文中進行。所以在繪圖之前需要獲取該上下文并傳入執行渲染的函數中。如果你正在渲染一副在內存中的圖片,此時就需要傳入圖片 所屬的上下文。獲得一個圖形上下文是我們完成繪圖任務的第一步,你可以將圖形上下文理解為一塊畫布。如果你沒有得到這塊畫布,那么你就無法完成任何繪圖操 作。

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

    1. 調用UIGraphicsBeginImageContextWithOptions函數;
    2. 利用cocoa為你生成的圖形上下文。當你子類化了一個UIView并實現了自己的drawRect:方法后,一旦drawRect:方法被調用,Cocoa就會為你創建一個圖形上下文,此時你對圖形上下文的所有繪圖操作都會顯示在UIView上。
    3. drawRect: inContext:。
  • 使用UIKit,你只能在當前上下文中繪圖,所以如果你當前處于 UIGraphicsBeginImageContextWithOptions函數或drawRect:方法中,你就可以直接使用UIKit提供的方法 進行繪圖。如果你持有一個context:參數,那么使用UIKit提供的方法之前,必須將該上下文參數轉化為當前上下文。幸運的是,調用 UIGraphicsPushContext 函數可以方便的將context:參數轉化為當前上下文,記住最后別忘了調用UIGraphicsPopContext函數恢復上下文環境。

  • 如果你當前處于 UIGraphicsBeginImageContextWithOptions函數或drawRect:方法中,并沒有引用一個上下文。為了使用 Core Graphics,你可以調用UIGraphicsGetCurrentContext函數獲得當前的圖形上下文。

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

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

2、 使用Core Graphics實現繪制藍色圓。

- (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:方法中實現繪圖任務。 drawLayer:inContext:方法是一個繪制圖層內容的代理方法。為了能夠調用drawLayer:inContext:方法,我們需要設定 圖層的代理對象。但要注意,不應該將UIView對象設置為顯示層的委托對象,這是因為UIView對象已經是隱式層的代理對象,再將它設置為另一個層的 委托對象就會出問題。輕量級的做法是:編寫負責繪圖形的代理類。在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]; // 調用此方法,drawLayer: inContext:方法才會被調用

4、 使用Core Graphics在drawLayer:inContext:方法中實現同樣操作,代碼如下:

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

5、 使用UIKit實現:

// 第一個參數表示所要創建的圖片的尺寸;第二個參 數用來指定所生成圖片的背景是否為不透明,如上我們使用YES而不是NO,則我們得到的圖片背景將會是黑色,顯然這不是我想要的;第三個參數指定生成圖片 的縮放因子,這個縮放因子與UIImage的scale屬性所指的含義是一致的。傳入0則表示讓圖片的縮放因子根據屏幕的分辨率而變化,所以我們得到的圖 片不管是在單分辨率還是視網膜屏上看起來都會很好。
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實現:

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 {
    // 創建一個基于位圖的上下文
    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 的區別
    UIGraphicsBeginImageContext is a UIKit wrapper that sits on top of CGBitmapContextCreate and reduces its functionality。也就是說UIGraphicsBeginImageContext高級一點,CGBitmapContextCreate底層一點,功能靈活點

CGContextWithAlpha解碼?


CGContextWithoutAlpha解碼?


ImageIO解碼

  • 步驟

    1. CGImageSourceCreateWithData(data) 創建 ImageSource。
    2. CGImageSourceCreateImageAtIndex(source) 創建一個未解碼的 CGImage。
    3. CGImageGetDataProvider(image) 獲取這個圖片的數據源。
    4. CGDataProviderCopyData(provider) 從數據源獲取直接解碼的數據。
      ImageIO 解碼發生在最后一步,這樣獲得的數據是沒有經過顏色類型轉換的原生數據(比如灰度圖像)。
  • 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;
        //  這里進行的解碼:CGDataProviderCopyData 內部會調用 ImageProviderCopyImageBlockSetCallback 和 copyImageBlock,得到的 CFDataRef 是解碼過的像素數組。
        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);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 繪制像素到屏幕上 answer-huang22 Mar 2014 分享文章 一個像素是如何繪制到屏幕上去的?有很多...
    阿貍旅途T恤閱讀 1,666評論 0 7
  • 卷首語 歡迎來到 objc.io 的第三期! 這一期都是關于視圖層的。當然視圖層有很多方面,我們需要把它們縮小到幾...
    評評分分閱讀 1,821評論 0 18
  • 一、Quartz 2D 中的數據管理 數據管理是每個圖形應用程序必須執行的任務。在 Quartz2D 中數據管理涉...
    尋形覓影閱讀 873評論 0 2
  • 人的一生中會有很多際遇,會碰到很多人,會發生很多故事,其中不乏會有一些不好的事情,人生不如意事十之八九,要常想一二...
    小毛猴閱讀 1,693評論 2 3
  • 一、財務分析報告的分類 財務分析報告從編寫的時間來劃分,可分為兩種:一是定期分析報告,二是非定期分析報告。定期分析...
    芳菲語閱讀 656評論 0 7