iOS图形绘制框架 ——UIBezierPath 、Core Graphics 和OpenGL

2018 年 6 月 8 日 CocoaChina

引子


PD:我们需要的界面大概是这样子的,可以实现吗?



技术:ok,这个界面很简单,我们用基本的view就可以实现。

数日后

UED:我们的设计是这样子的



技术:呃,晴天霹雳啊,为什么搞的这么复杂,又要弧度,又要曲线?为什么不能用标准的框架来


UED:这就是艺术,激烈而又平滑的过渡才能彰显她旺盛的生命力,而明暗错落的颜色赋予了她燃烧不尽的激情,仿佛一个少女,身材高挑、烈焰红唇......


技术:就不能少搞些奇技淫巧吗,我已经连吐槽的力气都没有了...


相信每个前端开发都有过这样的经历,我们认为可以用通用视图解决的需求,视觉非要加上点特殊的元素,不规则图形、不均匀颜色。做为一个有追求的Coder虽然嘴上说着不要,但身体却很诚实的去查资料。google一下iOS 绘图,各种绘图的技术简介和demo就搜出来了,随之而来的是各种意思好像都差不多,好像不是一回事的名词:UIBezierPath 、Quartz、Quartz 2D 、QuartzCore、Core Graphic、OpenGL 、OpenGL ES......


此时的我是这样子的



完全搞不懂到底在哪种场景下应该使用哪种技术,有木有。本文的目的就是梳理在iOS平台中供开发人员使用的绘图框架以及他们之间的恩怨纠葛,以便选择最合适的技术框架完成需求。


iOS、MacOS系统图形架构



由此图可见:


  • iOS提供了两套绘图框架,分别是UIBezierPath和Core Graphics。UIBezierPath属于UIKit。UIBezierPath是对Core Graphics框架的进一步封装。

  • OpenGL和Core Graphics都是绘图专用的API类族,调用图形处理器(GPU)进行图形的绘制和渲染。在架构上是平级的,相比UIkit更接近底层。


UIBezierPath


用于创建基于矢量的路径,如圆形、椭圆形和矩形,或者由多个直线和曲线组成的形状。绘图步骤也非常的简单:


  1. 重写drawRect方法

  2. 创建UIBezierPath对象

  3. 设置绘图属性,lineWidth

  4. 渲染


UIBezierPath绘制图形代码

//绘制圆形

- (void)drawRect:(CGRect)rect{
   UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
   
   //设置绘图属性
   path.lineWidth = 1;
   UIColor *color = [UIColor readColor];
   [color set];
 
   //按照路径绘制图形
   [path stroke];
}


Quartz


在介绍 Core Graphics 之前,我们先把 Quartz 的概念理顺清楚。嫌啰嗦可以直接拉到下面看结论


简单来说:

  1. Quartz由Quartz Compositor和Quartz 2D两部分组成

  2. Core Graphics是基于Quartz框架的2D绘图引擎。所以很多资料将Core Graphics称为Quartz是不准确的。

  3. Core Graphics同Quartz 2D是等价的。


那么Quartz同OpenGL是什么关系呢?他的底层是否通过OpenGL调用GPU?


结论


  1. CoreGraphics是基于Quartz框架的绘图引擎,同Quartz 2D是等价的。

  2. Quartz Extreme是针对Quartz底层的GPU加速。

  3. Quartz仅使用OpenGL的命令集,直接连接AGP(图形加速接口)

  4. Quartz在CPU上执行绘图命令,在GPU上最终合成成图形

  5. QuartzGL是Quartz 2D API的GPU加速。启用QuartzGL后,所有Quartz绘图命令都将转换为OpenGL命令并在GPU上执行。这个模式默认是关闭的

  6. Quartz在进行3D图形渲染时是基于OpenGL的,通过OpenGL连接AGP


Core Graphics


Core Graphics是基于Quartz框架的高保真输出2D图形的渲染引擎。可处理基于路径的绘图、抗锯齿渲染、渐变、图像、颜色管理、PDF文档等。 Core Graphics提供了一套2D绘图功能的C语言API,使用C结构体和C的函数模拟了一套面向对象的编程机制。Core Graphics中没有OC的对象和方法。


无论图片、PDF还是视图的图层,都是由CoreGraphics框架完成绘制的。UIImage、UIBezierPath和NSString都提供了至少一种用于在drawRect:中绘图的方法,实现原理是将Core Graphics代码封装在其中,降低绘图难度。


Context


CoreGraphics中最重要的对象是graphics context,既图形上下文。context是CGContextRef的对象,负责存储绘画状态和绘制内存所处的内存空间。


我以前一直无法很好的理解Context,后来接触Android绘图的时候发现Android没有这个东西。Android的2D图形绘制通过Canvas和Paint实现的。Android的绘图框架理解起来就非常的容易,你想画图首先你要有画的地方Canvas,其次你要有笔Paint。而在iOS中Context就是要画图的画板和画笔。


Core Graphics绘制图形代码

//绘制圆形
- (void)drawInContext:(CGContextRef)ctx
{
   //保存当前的绘图Context
   CGContextSaveGState(ctx);
   CGRect rectAngle = CGRectMake(0, 0, 100, 100);
   //添加一个椭圆路径
   CGContextAddEllipseInRect(ctx, rectAngle);
   //设置边框宽度
   CGContextSetLineWidth(ctx, self.ellipseBorderWidth);
   //设置边框颜色
   CGContextSetStrokeColorWithColor(ctx, self.ellipseBorderColor.CGColor);
   CGContextStrokePath(ctx);
   //设置填充色
   CGContextSetFillColorWithColor(ctx, self.ellipseFillColor.CGColor);
   CGContextFillEllipseInRect(ctx, rectAngle);
   
   CGContextRestoreGState(ctx);
}


Core Graphics的Ref后缀类型


带有Ref后缀的类型是CoreGraphics中用来模拟面向对象机制的C结构。CoreGraphics对象是在上分配内存,因此创建CoreGraphics对象时,会返回一个指向对象内存地址的指针


使用这种分配方式的C结构都有一个用来表示结构指针的类型定义(type definition)。例如,CGColor结构,不会被直接使用的类型,有一个表示Color * 的类型定义—CGColorRef,用来被使用的类型。使用这种类型定义是为了区分指针变量,方便开发者判断指针变量是指向C结构还是可以接收消息的Objective-C对象。


CGRect和CGPoint这种比较简单直接在栈上分配的结构体,不需要使用结构指针,因此类型名称后不带Ref后缀。


带有Ref后缀的类型的对象可能是强引用指针,成为指向对象的拥有者。ARC是无法识别Core对象的所有权,必须在使用后手动释放。规则是,如果使用名称中带有create或者copy的函数创建了一个CoreGraphics对象,就必须调用Release函数并传入该对象的指针。


Core Graphics 能完成的工作


  • 绘制图形 : 线条、三角形、矩形、圆、圆弧弧等

  • 绘制文字

  • 绘制生成图片(图像)

  • 读取生成PDF

  • 绘制渐变


Core Graphics相比UIBezierPath在使用上更复杂一些,但是支持的效果也更多,程序运行效率更高。所以现在iOS系统上绘图需求基本上都使用Core Graphis来完成。


OpenGL && OpenGL ES


OpenGL ES 是OpenGL 三维图形API的子集,针对嵌入式操作系统设计。iOS和Android都在系统内集成了OpenGL ES。所以针对OpenGL ES 的代码可以实现跨平台,大多数游戏框架都基于OpenGL。


OpenGL编程流程


1、编写shader脚本

  • 编写vertex shader

  • 编写fragment shader


2、创建shader实例

  • 创建 shader

  • 装载 shader

  • 编译 shader


3、创建program实例

  • 创建program

  • 装配shader

  • 链接program

  • 使用program


装载OpenGL shader 、创建OpenGL program示例代码

- (void)setupProgram
{
   NSString *vertexShaderPath  = [[NSBundle mainBundle] pathForResource:@"VertexShader" ofType:@"glsl"];
   NSString *fragmentShaderPath = [[NSBundle mainBundle] pathForResource:@"FragmentShader" ofType:@"glsl"];
   
   GLuint vertexShader = [GLESUtils loadShader:GL_VERTEX_SHADER withFilepath:vertexShaderPath];
   GLuint fragmentShader = [GLESUtils loadShader:GL_FRAGMENT_SHADER withFilepath:fragmentShaderPath];
   
   //Create program,attach shaders
   _programHandle = glCreateProgram();
   
   if (!_programHandle)
   {
       NSLog(@"Failed to create program");
       return;
   }
   
   glAttachShader(_programHandle, vertexShader);
   glAttachShader(_programHandle, fragmentShader);
   
   glLinkProgram(_programHandle);
   
   GLint linked;
   glGetProgramiv(_programHandle, GL_LINK_STATUS, &linked);
   
   if (!linked)
   {
       GLint infoLen = 0;
       glGetProgramiv(_programHandle, GL_INFO_LOG_LENGTH, &infoLen);
       
       if (infoLen > 1)
       {
           char *infoLog = malloc(sizeof(char) *infoLen);
           glGetProgramInfoLog(_programHandle, infoLen, NULL, infoLog);
           free(infoLog);
       }
       
       glDeleteProgram(_programHandle);
       _programHandle = 0;
       return;
   }
  
   glUseProgram(_programHandle);
   _positionSlot = glGetAttribLocation(_programHandle, "vPosition");
   
}


iOS系统使用OpenGL编程流程


1、设置CAEAGLLayer

CAEAGLLayer是iOS中用于呈现OpenGL ES的渲染内容的


2、设置EAGLContext

OpenGL ES 渲染上下文。这个context管理所有使用OpenGL ES 进行描绘的状态,命令以及资源信息。


3、创建Renderbuffer

缓冲区用于存储绘图数据,Render Buffer有三种类型,分别是color、depth、stencil buffer


4、创建Framebuffer object

Framebuffer object是buffer的管理者,color、depth、stencil可以添加到一个Framebuffer object上


5、销毁Renderbuffer和Framebuffer

当UIView变化后,layer的宽高也随之变化,导致原来的renderbuffer 不再相符,需要销毁既有 renderbuffer 和 framebuffer


iOS系统调用OpenGL示例代码

//在iOS设备中通过CAEAGLLayer使用OpenGL接口渲染内容
+ (Class)layerClass
{
   return [CAEAGLLayer class];
}

//设置呈现图层
- (void)setupLayer
{
   _eaglLayer = (CAEAGLLayer *)self.layer;
   _eaglLayer.opaque = YES;
   _eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
                                    kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat ,nil];
}

//设置图形上下文
- (void)setupContext
{
   EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
   _context = [[EAGLContext alloc] initWithAPI:api];
   
   if (!_context)
   {
       NSLog(@"Failed to initialize");
       exit(1);
   }
 
   if (![EAGLContext setCurrentContext:_context])
   {
       exit(1);
   }
}

//设置渲染缓冲区
- (void)setupRenderBuffer
{
   glGenRenderbuffers(1, &_colorRenderBuffer);
   glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
   [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}

//设置FrameBuffer
- (void)setupFrameBuffer
{
   glGenFramebuffers(1, &_frameBuffer);
   glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
   glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}

- (void)destoryRenderAndFrameBuffer
{
   glDeleteFramebuffers(1 , &_frameBuffer);
   _frameBuffer = 0;
   glDeleteRenderbuffers(1, &_colorRenderBuffer);
   _colorRenderBuffer = 0;
}


示例代码地址:

http://gitlab.alibaba-inc.com/xunfeng.zy/opengl/blob/master/OpenGLDemo/


结论


UIBezierPath的优势是方便,能用最少的代码得要想要的图形,并且不需要管理图形上下文、缓冲区等容易出问题的地方,只需要关注图形本身就行了。最主要的缺点是支持的效果有限,当需要实现一些复杂图形、复杂渐变效果的时候就无能为力了。所以如果只是一个简单的图形没有特别的要求,可以用UIBezierPath实现。


Core Graphics的功能就比UIBezierPath强大很多,使用起来也更复杂,而且需要自己管理图形上下文,需要投入更多的开发工作量。在效率和可做更多工作这两个方面上Core Graphics全面压制UIBezierPath,所以如果是复杂的图形、多个图形叠加、多种颜色渐变等需求可以使用Core Graphics实现。


OpenGL ES是直接操作GPU绘图,而Core Graphics框架是先把命令输入到CPU再调用GPU绘图。很明显OpenGL ES绘图的效率更高,使用OpenGL ES绘图也可以实现跨平台,而Core Graphics只能在iOS系统中使用。相比Core Graphics只支持2D绘图,OpenGL ES对2D\3D都有很好的支持。


可见OpenGL ES是全方面的碾压Core Graphics,但是除了特别大的图片运算需求外,我们很少使用OpenGL ES。其实上面的示例代码已经很好的说明了,绘制同一个图形UIBezierPath需要5行代码,Core Graphics需要10行代码,虽然Core Graphics明显比UIBezierPath工作量大,但还是同一个数量级的。


而使用OpenGL ES绘制,算上Vertex Shader和Fragment Shader绘制一个图形要300~400行代码。整整增加了两个数量级。再加上调试、debug的工作量,就算有跨平台的加成也无法抵消增加的时间成本。想使用OpenGL进行绘图,基本上都需要二次封装,这也是为什么我们在绘图的时候一般首选Core Graphics。


作者:darcy87

链接:https://www.jianshu.com/p/0cfd271fc6db


相关推荐:


登录查看更多
0

相关内容

> The Core Graphics framework is a C-based API that is based on the Quartz advanced drawing engine. It provides low-level, lightweight 2D rendering with unmatched output fidelity. You use this framework to handle path-based drawing, transformations, color management, offscreen rendering, patterns, gradients and shadings, image data management, image creation, masking, and PDF document creation, display, and parsing. via developer.apple.com/lib
【图神经网络(GNN)结构化数据分析】
专知会员服务
115+阅读 · 2020年3月22日
【新书】Java企业微服务,Enterprise Java Microservices,272页pdf
【电子书推荐】Data Science with Python and Dask
专知会员服务
43+阅读 · 2019年6月1日
Cayley图数据库的可视化(Visualize)
Python开发者
5+阅读 · 2019年9月9日
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
PyTorch & PyTorch Geometric图神经网络(GNN)实战
专知
81+阅读 · 2019年6月1日
已删除
将门创投
7+阅读 · 2019年3月28日
Revealing the Dark Secrets of BERT
Arxiv
4+阅读 · 2019年9月11日
Arxiv
19+阅读 · 2018年10月25日
VIP会员
相关资讯
Cayley图数据库的可视化(Visualize)
Python开发者
5+阅读 · 2019年9月9日
用 Python 开发 Excel 宏脚本的神器
私募工场
26+阅读 · 2019年9月8日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
PyTorch & PyTorch Geometric图神经网络(GNN)实战
专知
81+阅读 · 2019年6月1日
已删除
将门创投
7+阅读 · 2019年3月28日
Top
微信扫码咨询专知VIP会员