先說下背景,項(xiàng)目里需要繪制音樂和視頻的波形圖,由于產(chǎn)品上的設(shè)計,波形圖的長度基本都可以達(dá)到屏幕長度的幾十倍。并且圖形并不是折線圖而是柱狀圖,還要跟隨音樂音量變化,所以圖形肯定是無法直接拉伸擠壓的,所以當(dāng)時為了性能和內(nèi)存方面的考慮,嘗試了很多方案。
創(chuàng)新互聯(lián)建站堅持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站設(shè)計制作、成都網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時代的丹棱網(wǎng)站設(shè)計、移動媒體設(shè)計的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
UIImage:
使用UIGraphicsGetImageFromCurrentImageContext方法將繪制的圖形生成圖片。
這種方式適用于圖片不長并且圖片不變的情況。
優(yōu)點(diǎn):可在子線程繪制,方便緩存。
缺點(diǎn):占用內(nèi)存大,繪制不夠高效。
PS: 注意此方法有個隱患,因?yàn)橄到y(tǒng)會對設(shè)置給UIImageView的圖片進(jìn)行緩存,如果一直調(diào)用,即使是完全相同的圖片,也會產(chǎn)生內(nèi)存占用。
示例:
CALayer :
layer的基類,重寫drawInContext方法進(jìn)行進(jìn)行繪制。
優(yōu)點(diǎn):量級輕(實(shí)在想不到優(yōu)點(diǎn))。
缺點(diǎn):主線程繪制,繪制不夠高效 。
示例:
CATiledLayer:
layer的子類,專門用于繪制大圖的方案,系統(tǒng)底層已進(jìn)行過優(yōu)化,子線程繪制,并且不會繪制屏幕外的內(nèi)容。可將大圖分割成若干個更小的單元進(jìn)行繪制,可通過tileSize設(shè)置單個繪制單元的大小。
優(yōu)點(diǎn):子線程繪制,性能極好。
缺點(diǎn):繪制較緩慢,能控制的變量較少。
示例:
與CALayer用法一致,可設(shè)置額外屬性
YYAsyncLayer:
知名的異步繪制的第三方控件,是CALayer的子類,內(nèi)部創(chuàng)建了隊(duì)列進(jìn)行管理,能將繪制的操作轉(zhuǎn)換為異步操作,并且引入了RunLoop機(jī)制進(jìn)行管理,只在RunLoop空閑的時候才進(jìn)行刷新操作。
優(yōu)點(diǎn):子線程繪制,性能很高,不阻塞用戶操作。
缺點(diǎn):因?yàn)橹辉诳臻e時執(zhí)行,圖形刷新不及時。
示例:
CAShapeLayer + UIBezierPath:
layer的子類,CAshapeLayer能在GPU上渲染,性能很高,繪制速度很快,而且曲線繪制既能選擇在主線程繪制,也能選擇在子線程繪制。
優(yōu)點(diǎn):無論子線程還是主線程都可繪制,且性能很高。
缺點(diǎn):曲線路徑量大的話,還是影響性能。
示例:
最終還是選擇了CAShapeLayer + UIBezierPath方案,但是因?yàn)橐舨〝?shù)據(jù)量特別大(每秒40個音頻數(shù)據(jù)),導(dǎo)致多個圖形頻繁刷新的時候發(fā)熱十分嚴(yán)重,而且也出現(xiàn)了卡頓的情況。
為了優(yōu)化,最后又加入了分屏繪制的邏輯:
藍(lán)色區(qū)域表示屏幕區(qū)域,紅色表示繪制的區(qū)域,黑色線條表示臨界邊,
整體邏輯:
0、轉(zhuǎn)換坐標(biāo)為窗體坐標(biāo)。
1、判斷是否有上次繪制的位置,沒有則直接繪制。
2、繪制完成后保存當(dāng)前位置為繪制位置,計算出黑色臨臨界區(qū)域。
3、滑動視圖的過程中判斷滑動位置是否超出了黑線區(qū)域,超出則重新進(jìn)行繪制。
4、重復(fù)2、3。
最近項(xiàng)目中需要用到曲線圖,雖然有很多demo,但還是想自己寫個,畢竟也不難,當(dāng)然效果不如網(wǎng)上那些大神的好看~畢竟水平有限,但是也足夠我應(yīng)付項(xiàng)目需求了嘿嘿(主要還是閑的,哈哈)
首先效果如圖:
1.首先自定義一個view,我定義了這些屬性
(忽略我蹩腳的起名)
2.開始畫圖 首先根據(jù)x坐標(biāo)的個數(shù)畫出表格中的豎線及坐標(biāo)刻度
依葫蘆畫瓢得到眾橫線
接著根據(jù)實(shí)際值在表格中劃出紅點(diǎn)及實(shí)際坐標(biāo)值
其中以下是兩個懶加載
自定義的初始化方法:
動態(tài)連接各個點(diǎn),我讓這個行為在?秒內(nèi)執(zhí)行完
大功告成,直接就可以調(diào)用啦
demo地址:
[img]UIBezierPath 可以創(chuàng)建基于矢量的路徑,例如橢圓或者矩形,或者有多個直線和曲線段組成的形狀。
使用 UIBezierPath ,你只能在當(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)境。
簡言之:我們一般使用 UIBezierPath 都是在重寫的 drawRecrt: 方法這種情形。其繪圖的步驟是這樣的:
1.重寫 drawRect: 方法。但不需要我們自己獲取當(dāng)前上下文 context ;
2.創(chuàng)建相應(yīng)圖形的 UIBezierPath 對象,并設(shè)置一些修飾屬性;
3.渲染,完成繪制。
繪制矩形最簡單的辦法是使用 UIRectFrame 和 UIRectFill
通過使用 UIBezierPath 可以自定義繪制線條的粗細(xì),是否圓角等。
多邊形是一些簡單的形狀,這些形狀是由一些直線線條組成,我們可以用 moveToPoint: 和 addLineToPoint: 方法去構(gòu)建。 moveToPoint: 設(shè)置我們想要創(chuàng)建形狀的起點(diǎn)。從這點(diǎn)開始,我們可以用方法 addLineToPoint: 去創(chuàng)建一個形狀的線段。
我們可以連續(xù)的創(chuàng)建 line,每一個 line 的起點(diǎn)都是先前的終點(diǎn),終點(diǎn)就是指定的點(diǎn)。
closePath 可以在最后一個點(diǎn)和第一個點(diǎn)之間畫一條線段。
想畫弧線組成的不規(guī)則形狀,我們需要使用中心點(diǎn)、弧度和半徑,如下圖?;《仁褂庙槙r針腳底,0弧度指向右邊,pi/2指向下方,pi指向左邊,-pi/2指向上方。然后使用 bezierPathWithArcCenter: radius: startAngle endAngle: clockwise: 方法來繪制。
如果要研究OpenGL ES相關(guān)和 GPU 相關(guān),這篇文章很具有參考的入門價值.
首先要從 Runloop 開始說,iOS 的 MainRunloop 是一個60fps 的回調(diào),也就是說16.7ms(毫秒)會繪制一次屏幕,這個時間段內(nèi)要完成:
這些 CPU 的工作.
然后將這個緩沖區(qū)交給 GPU 渲染, 這個過程又包含:
最終現(xiàn)實(shí)在屏幕上.因此,如果在16.7ms 內(nèi)完不成這些操作, eg: CPU做了太多的工作, 或者 view 層次過于多,圖片過于大,導(dǎo)致 GPU 壓力太大,就會導(dǎo)致"卡"的現(xiàn)象,也就是 丟幀 , 掉幀 .
蘋果官方給出的最佳幀率是: 60fps (60Hz),也就是一幀不丟, 當(dāng)然這是理想中的絕佳體驗(yàn).
一般來說如果幀率達(dá)到 60+fps (fps = 60幀以上,如果幀率fps 50,人眼就基本感覺不到卡頓了,因此,如果你能讓你的 iOS 程序 穩(wěn)定 保持在 60fps 已經(jīng)很不錯了, 注釋,是"穩(wěn)定"在60fps,而不是, 10fps , 40fps , 20fps 這樣的跳動,如果幀頻不穩(wěn)就會有卡的感覺, 60fps 真的很難達(dá)到, 尤其是在 iPhone 4/4s等 32bit 位機(jī)上,不過現(xiàn)在蘋果已經(jīng)全面放棄32位,支持最低64位會好很多.
總的來說, UIView從繪制到Render的過程有如下幾步:
UIView 的繪制和渲染是兩個過程:
上面提到的從 CPU 到 GPU 的過程可用下圖表示:
下面具體來討論下這個過程
假設(shè)我們創(chuàng)建一個 UILabel
這個時候不會發(fā)生任何操作, 由于 UILabel 重寫了 drawRect 方法,因此,這個 View 會被 marked as "dirty" :
類似這個樣子:
然后一個新的 Runloop 到來,上面說道在這個 Runloop 中需要將界面渲染上去,對于 UIKit 的渲染,Apple用的是它的 Core Animation 。 做法是在Runloop開始的時候調(diào)用:
在 Runloop 結(jié)束的時候調(diào)用
在 begin 和 commit 之間做的事情是將 view 增加到 view hierarchy 中,這個時候也不會發(fā)生任何繪制的操作。 當(dāng) [CATransaction commit] 執(zhí)行完后, CPU 開始繪制這個 view :
首先 CPU 會為 layer 分配一塊內(nèi)存用來繪制 bitmap ,叫做 backing store
創(chuàng)建指向這塊 bitmap 緩沖區(qū)的指針,叫做 CGContextRef
通過 Core Graphic 的 api ,也叫 Quartz2D ,繪制 bitmap
將 layer 的 content 指向生成的 bitmap
清空 dirty flag 標(biāo)記
這樣 CPU 的繪制基本上就完成了.
通過 time profiler 可以完整的看到個過程:
假如某個時刻修改了 label 的 text :
由于內(nèi)容變了, layer 的 content 的 bitmap 的尺寸也要變化,因此這個時候當(dāng)新的 Runloop 到來時, CPU 要為 layer 重新創(chuàng)建一個 backing store ,重新繪制 bitmap .
CPU 這一塊最耗時的地方往往在 Core Graphic 的繪制上,關(guān)于 Core Graphic 的性能優(yōu)化是另一個話題了,又會牽扯到很多東西,就不在這里討論了.
GPU bound:
CPU 完成了它的任務(wù):將 view 變成了 bitmap ,然后就是 GPU 的工作了, GPU 處理的單位是 Texture .
基本上我們控制 GPU 都是通過 OpenGL 來完成的,但是從 bitmap 到 Texture 之間需要一座橋梁, Core Animation 正好充當(dāng)了這個角色:
Core Animation 對 OpenGL 的 api 有一層封裝,當(dāng)我們要渲染的 layer 已經(jīng)有了 bitmap content 的時候,這個 content 一般來說是一個 CGImageRef , CoreAnimation 會創(chuàng)建一個 OpenGL 的 Texture 并將 CGImageRef(bitmap) 和這個 Texture 綁定,通過 TextureID 來標(biāo)識。
這個對應(yīng)關(guān)系建立起來之后,剩下的任務(wù)就是 GPU 如何將 Texture 渲染到屏幕上了。 GPU 大致的工作模式如下:
整個過程也就是一件事:
CPU 將準(zhǔn)備好的 bitmap 放到 RAM 里, GPU 去搬這快內(nèi)存到 VRAM 中處理。 而這個過程 GPU 所能承受的極限大概在16.7ms完成一幀的處理,所以最開始提到的60fps其實(shí)就是GPU能處理的最高頻率.
因此, GPU 的挑戰(zhàn)有兩個:
這兩個中瓶頸基本在第二點(diǎn)上。渲染 Texture 基本要處理這么幾個問題:
Compositing 是指將多個紋理拼到一起的過程,對應(yīng) UIKit ,是指處理多個 view 合到一起的情況,如:
如果 view 之間沒有疊加,那么 GPU 只需要做普通渲染即可.
如果多個 view 之間有疊加部分, GPU 需要做 blending .
加入兩個 view 大小相同,一個疊加在另一個上面,那么計算公式如下:
R = S + D *( 1 - Sa )
其中 S , D 都已經(jīng) pre-multiplied 各自的 alpha 值。
Sa 代表 Texture 的 alpha 值。
假如 Top Texture (上層 view )的 alpha 值為 1 ,即不透明。那么它會遮住下層的 Texture .
即, R = S 。是合理的。
假如 Top Texture (上層 view )的 alpha 值為 0.5 ,
S 為 (1,0,0) ,乘以 alpha 后為 (0.5,0,0) 。
D 為 (0,0,1) 。
得到的 R 為 (0.5,0,0.5) 。
基本上每個像素點(diǎn)都需要這么計算一次。
因此, view 的層級很復(fù)雜,或者 view 都是半透明的( alpha 值不為 1 )都會帶來 GPU 額外的計算工作。
這個問題,主要是處理 image 帶來的,假如內(nèi)存里有一張 400x400 的圖片,要放到 100x100 的 imageview 里,如果不做任何處理,直接丟進(jìn)去,問題就大了,這意味著, GPU 需要對大圖進(jìn)行縮放到小的區(qū)域顯示,需要做像素點(diǎn)的 sampling ,這種 smapling 的代價很高,又需要兼顧 pixel alignment 。 計算量會飆升。
如果我們對 layer 做這樣的操作:
會產(chǎn)生 offscreen rendering ,它帶來的最大的問題是,當(dāng)渲染這樣的 layer 的時候,需要額外開辟內(nèi)存,繪制好 radius,mask ,然后再將繪制好的 bitmap 重新賦值給 layer 。
因此繼續(xù)性能的考慮, Quartz 提供了優(yōu)化的 api :
簡單的說,這是一種 cache 機(jī)制。
同樣 GPU 的性能也可以通過 instrument 去衡量:
紅色代表 GPU 需要做額外的工作來渲染 View ,綠色代表 GPU 無需做額外的工作來處理 bitmap 。
全文完
收錄: 原文地址
這種繪制是根據(jù)圖片的像素比例 等比例進(jìn)行繪制的,在選擇圖片和創(chuàng)建展示圖片的imageView 時,注意查看尺寸 注:繪圖時使用 ?[UIScreen mainScreen].scale 可以是圖片更清晰?UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//這樣就不模糊了
//圖片上添加文字 詳細(xì)版
- (UIImage*)text:(NSString*)text addToView:(UIImage*)image{
//設(shè)置字體樣式
UIFont*font = [UIFont fontWithName:@"Arial-BoldItalicMT"size:100];
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:text];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:NSMakeRange(0, 1)];
[str addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:100] range:NSMakeRange(0, text.length)];
//? ? CGSize textSize = [text sizeWithAttributes:dict];
CGSize textSize = [str size];
//繪制上下文
UIGraphicsBeginImageContext(image.size);
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//這樣就不模糊了
[image drawInRect:CGRectMake(0,0, image.size.width, image.size.height)];
//? ? int border =10;
CGRect re = {CGPointMake((image.size.width- textSize.width)/2, 200), textSize};
//? ? CGRect rect = CGRectMake(0, 0, image.size.width, 500);
//此方法必須寫在上下文才生效
[str drawInRect:re ];
UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
//修改圖片尺寸
- (UIImage*)imageWithImageSimple:(UIImage*)image scaledToSize:(CGSize)newSize
{
// Create a graphics image context
UIGraphicsBeginImageContext(newSize);//這樣壓縮的圖片展示出來會很模糊
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//這樣就不模糊了
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//這樣就不模糊了
// Tell the old image to draw in this new context, with the desired
// new size
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
// Get the new image from the context
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
// End the context
UIGraphicsEndImageContext();
// Return the new image.
return newImage;
}
//圓角
- (UIImage *) getRadioImaeg:(NSString *)imageName1{
UIImage *image1 = [UIImage imageNamed:imageName1];
UIGraphicsBeginImageContextWithOptions(image1.size, 0, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(00, 0, image1.size.width, image1.size.width);
CGContextAddEllipseInRect(ctx, rect);
CGContextClip(ctx);
[image1 drawInRect:rect];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
//圖片疊加
- (UIImage *)addImage:(NSString *)imageName1 withImage:(NSString *)imageName2 {
UIImage *image1 = [UIImage imageNamed:imageName1];
UIImage *image2 = [self getRadioImaeg:@"333"];
UIGraphicsBeginImageContext(image1.size);
//UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//這樣就不模糊了
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
[image2 drawInRect:CGRectMake((image1.size.width - image2.size.width)/2,(image1.size.height - image2.size.height)/2, image2.size.width, image2.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage;
}