十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
先说下背景,项目里需要绘制音乐和视频的波形图,由于产品上的设计,波形图的长度基本都可以达到屏幕长度的几十倍。并且图形并不是折线图而是柱状图,还要跟随音乐音量变化,所以图形肯定是无法直接拉伸挤压的,所以当时为了性能和内存方面的考虑,尝试了很多方案。
创新互联建站坚持“要么做到,要么别承诺”的工作理念,服务领域包括:网站设计制作、成都网站设计、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的丹棱网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
UIImage:
使用UIGraphicsGetImageFromCurrentImageContext方法将绘制的图形生成图片。
这种方式适用于图片不长并且图片不变的情况。
优点:可在子线程绘制,方便缓存。
缺点:占用内存大,绘制不够高效。
PS: 注意此方法有个隐患,因为系统会对设置给UIImageView的图片进行缓存,如果一直调用,即使是完全相同的图片,也会产生内存占用。
示例:
CALayer :
layer的基类,重写drawInContext方法进行进行绘制。
优点:量级轻(实在想不到优点)。
缺点:主线程绘制,绘制不够高效 。
示例:
CATiledLayer:
layer的子类,专门用于绘制大图的方案,系统底层已进行过优化,子线程绘制,并且不会绘制屏幕外的内容。可将大图分割成若干个更小的单元进行绘制,可通过tileSize设置单个绘制单元的大小。
优点:子线程绘制,性能极好。
缺点:绘制较缓慢,能控制的变量较少。
示例:
与CALayer用法一致,可设置额外属性
YYAsyncLayer:
知名的异步绘制的第三方控件,是CALayer的子类,内部创建了队列进行管理,能将绘制的操作转换为异步操作,并且引入了RunLoop机制进行管理,只在RunLoop空闲的时候才进行刷新操作。
优点:子线程绘制,性能很高,不阻塞用户操作。
缺点:因为只在空闲时执行,图形刷新不及时。
示例:
CAShapeLayer + UIBezierPath:
layer的子类,CAshapeLayer能在GPU上渲染,性能很高,绘制速度很快,而且曲线绘制既能选择在主线程绘制,也能选择在子线程绘制。
优点:无论子线程还是主线程都可绘制,且性能很高。
缺点:曲线路径量大的话,还是影响性能。
示例:
最终还是选择了CAShapeLayer + UIBezierPath方案,但是因为音波数据量特别大(每秒40个音频数据),导致多个图形频繁刷新的时候发热十分严重,而且也出现了卡顿的情况。
为了优化,最后又加入了分屏绘制的逻辑:
蓝色区域表示屏幕区域,红色表示绘制的区域,黑色线条表示临界边,
整体逻辑:
0、转换坐标为窗体坐标。
1、判断是否有上次绘制的位置,没有则直接绘制。
2、绘制完成后保存当前位置为绘制位置,计算出黑色临临界区域。
3、滑动视图的过程中判断滑动位置是否超出了黑线区域,超出则重新进行绘制。
4、重复2、3。
最近项目中需要用到曲线图,虽然有很多demo,但还是想自己写个,毕竟也不难,当然效果不如网上那些大神的好看~毕竟水平有限,但是也足够我应付项目需求了嘿嘿(主要还是闲的,哈哈)
首先效果如图:
1.首先自定义一个view,我定义了这些属性
(忽略我蹩脚的起名)
2.开始画图 首先根据x坐标的个数画出表格中的竖线及坐标刻度
依葫芦画瓢得到众横线
接着根据实际值在表格中划出红点及实际坐标值
其中以下是两个懒加载
自定义的初始化方法:
动态连接各个点,我让这个行为在️秒内执行完
大功告成,直接就可以调用啦
demo地址:
[img]UIBezierPath 可以创建基于矢量的路径,例如椭圆或者矩形,或者有多个直线和曲线段组成的形状。
使用 UIBezierPath ,你只能在当前上下文中绘图,所以如果你当前处于 UIGraphicsBeginImageContextWithOptions 函数或 drawRect: 方法中,你就可以直接使用UIKit提供的方法进行绘图。如果你持有一个 context: 参数,那么使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。幸运的是,调用 UIGraphicsPushContext 函数可以方便的将 context: 参数转化为当前上下文,记住最后别忘了调用 UIGraphicsPopContext 函数恢复上下文环境。
简言之:我们一般使用 UIBezierPath 都是在重写的 drawRecrt: 方法这种情形。其绘图的步骤是这样的:
1.重写 drawRect: 方法。但不需要我们自己获取当前上下文 context ;
2.创建相应图形的 UIBezierPath 对象,并设置一些修饰属性;
3.渲染,完成绘制。
绘制矩形最简单的办法是使用 UIRectFrame 和 UIRectFill
通过使用 UIBezierPath 可以自定义绘制线条的粗细,是否圆角等。
多边形是一些简单的形状,这些形状是由一些直线线条组成,我们可以用 moveToPoint: 和 addLineToPoint: 方法去构建。 moveToPoint: 设置我们想要创建形状的起点。从这点开始,我们可以用方法 addLineToPoint: 去创建一个形状的线段。
我们可以连续的创建 line,每一个 line 的起点都是先前的终点,终点就是指定的点。
closePath 可以在最后一个点和第一个点之间画一条线段。
想画弧线组成的不规则形状,我们需要使用中心点、弧度和半径,如下图。弧度使用顺时针脚底,0弧度指向右边,pi/2指向下方,pi指向左边,-pi/2指向上方。然后使用 bezierPathWithArcCenter: radius: startAngle endAngle: clockwise: 方法来绘制。
如果要研究OpenGL ES相关和 GPU 相关,这篇文章很具有参考的入门价值.
首先要从 Runloop 开始说,iOS 的 MainRunloop 是一个60fps 的回调,也就是说16.7ms(毫秒)会绘制一次屏幕,这个时间段内要完成:
这些 CPU 的工作.
然后将这个缓冲区交给 GPU 渲染, 这个过程又包含:
最终现实在屏幕上.因此,如果在16.7ms 内完不成这些操作, eg: CPU做了太多的工作, 或者 view 层次过于多,图片过于大,导致 GPU 压力太大,就会导致"卡"的现象,也就是 丢帧 , 掉帧 .
苹果官方给出的最佳帧率是: 60fps (60Hz),也就是一帧不丢, 当然这是理想中的绝佳体验.
一般来说如果帧率达到 60+fps (fps = 60帧以上,如果帧率fps 50,人眼就基本感觉不到卡顿了,因此,如果你能让你的 iOS 程序 稳定 保持在 60fps 已经很不错了, 注释,是"稳定"在60fps,而不是, 10fps , 40fps , 20fps 这样的跳动,如果帧频不稳就会有卡的感觉, 60fps 真的很难达到, 尤其是在 iPhone 4/4s等 32bit 位机上,不过现在苹果已经全面放弃32位,支持最低64位会好很多.
总的来说, UIView从绘制到Render的过程有如下几步:
UIView 的绘制和渲染是两个过程:
上面提到的从 CPU 到 GPU 的过程可用下图表示:
下面具体来讨论下这个过程
假设我们创建一个 UILabel
这个时候不会发生任何操作, 由于 UILabel 重写了 drawRect 方法,因此,这个 View 会被 marked as "dirty" :
类似这个样子:
然后一个新的 Runloop 到来,上面说道在这个 Runloop 中需要将界面渲染上去,对于 UIKit 的渲染,Apple用的是它的 Core Animation 。 做法是在Runloop开始的时候调用:
在 Runloop 结束的时候调用
在 begin 和 commit 之间做的事情是将 view 增加到 view hierarchy 中,这个时候也不会发生任何绘制的操作。 当 [CATransaction commit] 执行完后, CPU 开始绘制这个 view :
首先 CPU 会为 layer 分配一块内存用来绘制 bitmap ,叫做 backing store
创建指向这块 bitmap 缓冲区的指针,叫做 CGContextRef
通过 Core Graphic 的 api ,也叫 Quartz2D ,绘制 bitmap
将 layer 的 content 指向生成的 bitmap
清空 dirty flag 标记
这样 CPU 的绘制基本上就完成了.
通过 time profiler 可以完整的看到个过程:
假如某个时刻修改了 label 的 text :
由于内容变了, layer 的 content 的 bitmap 的尺寸也要变化,因此这个时候当新的 Runloop 到来时, CPU 要为 layer 重新创建一个 backing store ,重新绘制 bitmap .
CPU 这一块最耗时的地方往往在 Core Graphic 的绘制上,关于 Core Graphic 的性能优化是另一个话题了,又会牵扯到很多东西,就不在这里讨论了.
GPU bound:
CPU 完成了它的任务:将 view 变成了 bitmap ,然后就是 GPU 的工作了, GPU 处理的单位是 Texture .
基本上我们控制 GPU 都是通过 OpenGL 来完成的,但是从 bitmap 到 Texture 之间需要一座桥梁, Core Animation 正好充当了这个角色:
Core Animation 对 OpenGL 的 api 有一层封装,当我们要渲染的 layer 已经有了 bitmap content 的时候,这个 content 一般来说是一个 CGImageRef , CoreAnimation 会创建一个 OpenGL 的 Texture 并将 CGImageRef(bitmap) 和这个 Texture 绑定,通过 TextureID 来标识。
这个对应关系建立起来之后,剩下的任务就是 GPU 如何将 Texture 渲染到屏幕上了。 GPU 大致的工作模式如下:
整个过程也就是一件事:
CPU 将准备好的 bitmap 放到 RAM 里, GPU 去搬这快内存到 VRAM 中处理。 而这个过程 GPU 所能承受的极限大概在16.7ms完成一帧的处理,所以最开始提到的60fps其实就是GPU能处理的最高频率.
因此, GPU 的挑战有两个:
这两个中瓶颈基本在第二点上。渲染 Texture 基本要处理这么几个问题:
Compositing 是指将多个纹理拼到一起的过程,对应 UIKit ,是指处理多个 view 合到一起的情况,如:
如果 view 之间没有叠加,那么 GPU 只需要做普通渲染即可.
如果多个 view 之间有叠加部分, GPU 需要做 blending .
加入两个 view 大小相同,一个叠加在另一个上面,那么计算公式如下:
R = S + D *( 1 - Sa )
其中 S , D 都已经 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) 。
基本上每个像素点都需要这么计算一次。
因此, view 的层级很复杂,或者 view 都是半透明的( alpha 值不为 1 )都会带来 GPU 额外的计算工作。
这个问题,主要是处理 image 带来的,假如内存里有一张 400x400 的图片,要放到 100x100 的 imageview 里,如果不做任何处理,直接丢进去,问题就大了,这意味着, GPU 需要对大图进行缩放到小的区域显示,需要做像素点的 sampling ,这种 smapling 的代价很高,又需要兼顾 pixel alignment 。 计算量会飙升。
如果我们对 layer 做这样的操作:
会产生 offscreen rendering ,它带来的最大的问题是,当渲染这样的 layer 的时候,需要额外开辟内存,绘制好 radius,mask ,然后再将绘制好的 bitmap 重新赋值给 layer 。
因此继续性能的考虑, Quartz 提供了优化的 api :
简单的说,这是一种 cache 机制。
同样 GPU 的性能也可以通过 instrument 去衡量:
红色代表 GPU 需要做额外的工作来渲染 View ,绿色代表 GPU 无需做额外的工作来处理 bitmap 。
全文完
收录: 原文地址
这种绘制是根据图片的像素比例 等比例进行绘制的,在选择图片和创建展示图片的imageView 时,注意查看尺寸 注:绘图时使用 [UIScreen mainScreen].scale 可以是图片更清晰 UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);//这样就不模糊了
//图片上添加文字 详细版
- (UIImage*)text:(NSString*)text addToView:(UIImage*)image{
//设置字体样式
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;
}