- 誰吃掉我們的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 操作可能會涉及以下部分或全部步驟:
- 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作;
- 將文件數(shù)據(jù)從磁盤讀到內(nèi)存中旭旭;
- 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式谎脯,這是一個非常耗時(shí)的 CPU 操作;
- 最后 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ù)組咸产,而像素格式則是用來描述每個像素的組成格式,它包括以下信息:
- Bits per component :一個像素中每個獨(dú)立的顏色分量使用的 bit 數(shù)仲闽。
- Bits per pixel :一個像素使用的總 bit 數(shù)脑溢。
- Bytes per row :位圖中的每一行使用的字節(jié)數(shù)。
- Color and Color Spaces:色彩空間。如0,0,1屑彻,顏色空間則是用來說明如何解析這些值的验庙,在RGB中代表藍(lán)色。
- 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é)順序始終與其保持一致)掂咒。
- CGImageByteOrderInfo:像素字節(jié)順序。提供2方面信息:小端模式還是大端模式迈喉;數(shù)據(jù)以 16 位還是 32 位為單位绍刮。
-
函數(shù)中參數(shù)的含義
- data :如果不為 NULL ,那么它應(yīng)該指向一塊大小至少為 bytesPerRow * height 字節(jié)的內(nèi)存弊添;如果 為 NULL 录淡,那么系統(tǒng)就會為我們自動分配和釋放所需的內(nèi)存捌木,所以一般指定 NULL 即可油坝;
- width 和 height :位圖的寬度和高度,分別賦值為圖片的像素寬度和像素高度即可刨裆;
- bitsPerComponent :像素的每個顏色分量使用的 bit 數(shù)澈圈,在 RGB 顏色空間下指定 8 即可;
- 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? 疯坤。
- space :就是我們前面提到的顏色空間报慕,一般使用 RGB 即可。
- 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性能對比
-
參數(shù)設(shè)置
- Github Demo
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é)論==
- CGImageSourceRef抽象了對讀圖像數(shù)據(jù)的通道逞频,讀取圖像要通過它,它自己本身不讀取圖像的任何數(shù)據(jù)栋齿,在你調(diào)用CGImageSourceCopyPropertiesAtIndex的時(shí)候會才去讀取圖像元數(shù)據(jù)苗胀。
- CGImageMetadataRef抽象出圖片中EXIF、IPTC瓦堵、XMP格式的元數(shù)據(jù)基协,通過CGImageSourceRef獲取。
- CGImageRef抽象了圖像的基本數(shù)據(jù)和元數(shù)據(jù)菇用,創(chuàng)建的時(shí)候會通過CGImageSourceRef去讀取圖像的基礎(chǔ)數(shù)據(jù)和元數(shù)據(jù)澜驮,但沒有讀取圖像的其他數(shù)據(jù),沒有做圖片解碼的動作惋鸥。
- CGDataProviderRef沒有得出有用信息杂穷。
- 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口叙。
優(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ǔ)充:
光柵化:雙刃劍
- TableViewCell的重繪是很頻繁的(因?yàn)镃ell的復(fù)用),如果Cell的內(nèi)容不斷變化,則Cell需要不斷重繪,如果此時(shí)設(shè)置了cell.layer可光柵化草描。則會造成大量的offscreen渲染,降低圖形性能。
- 當(dāng)然,合理利用的話,是能夠得到不少性能的提高的,因?yàn)槭褂胹houldRasterize后layer會緩存為Bitmap位圖,對一些添加了shawdow等效果的耗費(fèi)資源較多的靜態(tài)內(nèi)容進(jìn)行緩存,能夠得到性能的提升策严。
離屏渲染
- 指的是在圖像在繪制到當(dāng)前屏幕前,需要先進(jìn)行一次渲染,之后才繪制到當(dāng)前屏幕穗慕。
- 離屏渲染會消耗大量資源:GPU需要另外alloc一塊內(nèi)存(CPU)來進(jìn)行渲染(就是使用CPU進(jìn)行渲染),渲染完畢后在繪制到當(dāng)前屏幕,而且對于顯卡來說,onscreen到offscreen的上下文環(huán)境切換是非常昂貴的(這個過程的消耗會比較昂貴,涉及到OpenGL的pipeline跟barrier)妻导。
- 使用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直接顯示到屏幕上。
- 使用GPU造成離屏渲染操作包括:設(shè)置cornerRadius, masks, shadows,edge antialiasing(view的縮放的時(shí)候鲤孵,layer.border.width隨著view的放大壶栋,會出現(xiàn)鋸齒化的問題),layer.edgeAntialiasingMask等普监;開啟光柵化:設(shè)置layer.shouldRasterize = YES贵试;
- 解決辦法:
使用Instruments
- 混合層檢查:Color Blended layers。標(biāo)示混合的圖層會為紅色,不透明的圖層為綠色凯正,通常我們希望綠色的區(qū)域越多越好毙玻。
- 開啟光柵化是否命中緩存:Color Hits Green and Misses Red。設(shè)置viewlayer的shouldRasterize為YES廊散,那些成功被緩存的layer會標(biāo)注為綠色桑滩,反之為紅色。
- Color copied images:表示那些被Core Animation拷貝的圖片允睹。這主要是因?yàn)樵搱D片的色彩格式不能被GPU直接處理运准,需要在CPU這邊做轉(zhuǎn)換,假如在主線層做這個操作對性能會有一定的影響缭受。
- Color misaligned images:被縮放的圖片會被標(biāo)記為黃色,像素不對齊則會標(biāo)注為紫色胁澳。
- 離屏渲染: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ù)料的妻顶。
繪圖
Core Graphics Framework是一套基于C的API框架酸员,使用了Quartz作為繪圖引擎。它提供了低級別讳嘱、輕量級幔嗦、高保真度的2D渲染。該框架可以用于基于路徑的 繪圖沥潭、變換邀泉、顏色管理、脫屏渲染钝鸽,模板汇恤、漸變、遮蔽拔恰、圖像數(shù)據(jù)管理因谎、圖像的創(chuàng)建、遮罩以及PDF文檔的創(chuàng)建颜懊、顯示和分析财岔。
-
iOS支持兩套圖形API族:Core Graphics/QuartZ 2D 和OpenGL ES/OpenGL。
- Core Graphics是一個繪圖專用的API族河爹,它經(jīng)常被稱為QuartZ或QuartZ 2D匠璧。Core Graphics是iOS上所有繪圖功能的基石,包括UIKit咸这。QuartZ 2D是蘋果公司開發(fā)的一套API夷恍,它是Core Graphics Framework的一部分。
- 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ù)的第一步磺芭,你可以將圖形上下文理解為一塊畫布。如果你沒有得到這塊畫布醉箕,那么你就無法完成任何繪圖操 作钾腺。
-
兩種最為常用的獲取上下文方法:
- 調(diào)用UIGraphicsBeginImageContextWithOptions函數(shù)徙垫;
- 利用cocoa為你生成的圖形上下文。當(dāng)你子類化了一個UIView并實(shí)現(xiàn)了自己的drawRect:方法后放棒,一旦drawRect:方法被調(diào)用姻报,Cocoa就會為你創(chuàng)建一個圖形上下文,此時(shí)你對圖形上下文的所有繪圖操作都會顯示在UIView上间螟。
- 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解碼
-
步驟
- CGImageSourceCreateWithData(data) 創(chuàng)建 ImageSource尚粘。
- CGImageSourceCreateImageAtIndex(source) 創(chuàng)建一個未解碼的 CGImage择卦。
- CGImageGetDataProvider(image) 獲取這個圖片的數(shù)據(jù)源。
- 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);