Core Graphics 一: CGContext基本绘制
概览
硬件 -> OpenGL/core graphics -> core animation -> UI Kit/ App Kit
底层到上层
先看一下文档,打开xcode help的developer documentation
core graphics的api
先从最主要的部分进入
1.CGContext
Graphics Context:图形上下文,也就是画布,绘制完成后,将画布放到view中显示.
主要分为绘制,管理和配置等几个部分.
获取画布(CGContextRef对象)可以在uiview中重写- (void)drawRect:(CGRect)rect,也可以获取CALayer的画布,这里先从drawRect方法开始.
CGContext的绘制是绘制图形,基本上由路径的构建和填充策略组成
2.构建路径
- 从画一条线开始CGContextAddLineToPoint
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100); //画笔移动到(100,100)
CGContextAddLineToPoint(context, 200, 100); //向(200,100)画一条线
CGContextDrawPath(context, kCGPathStroke);
kCGPathStroke是CGPathDrawingMode枚举,后面详细介绍
- 画圆弧CGContextAddArc
void CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise);
xy是圆心坐标,radius是半径,startAngle是起始的角度,endAngle是结束的角度,clockwise是画圆弧的方向顺序,1为顺时针,0是逆时针.
CGContextAddArc(context, 100, 200, 50, M_PI/2, M_PI/2*3, 1);
CGContextDrawPath(context, kCGPathStroke);
如果是顺时针,则从圆心左边开始,为0,顺时针,往上90度为M_PI/2,以此类推,如果是逆时针,则从圆心右边开始,逆时针往上90度为M_PI/2,以此类推.
圆弧CGContextAddArcToPoint函数
CGContextMoveToPoint(context, 190, 190);
CGContextAddArcToPoint(context, 200, 200, 300, 50, 100);
CGContextDrawPath(context, kCGPathStroke);
这个方法是从currentPoint(A)开始,再给定两个点B,C,然后再给一个半径,会画出一个和AB,BC两条线相切的圆弧以及A到圆弧的线段这样一个图形,
CGContextMoveToPoint和CGContextAddLineToPoint都会产生currentPoint.
这个方法是用来解决图形上拼接圆弧的场景,只使用CGContextAddArc有时候很难把线段和圆弧连接上.
- 画曲线
即贝塞尔曲线
CGContextAddQuadCurveToPoint,二阶贝塞尔曲线,画出来的就是一个抛物线
CGContextMoveToPoint(context, 100, 290);
CGContextAddQuadCurveToPoint(context, 150, 80, 200, 390);
CGContextDrawPath(context, kCGPathStroke);
用两个点确定的一条曲线,和currentPoint组成两条线,与曲线相切
CGContextAddQuadCurveToPoint
二阶动画
CGContextAddCurveToPoint,三阶贝塞尔曲线
CGContextMoveToPoint(context, 300, 50);
CGContextAddCurveToPoint(context, 20, 300, 100, 300, 150, 350);
CGContextDrawPath(context, kCGPathStroke);
三个点可以确定一个曲线,当然这里没有算上currentPoint
CGContextAddCurveToPoint
三阶动画
- 添加多条直线CGContextAddLines
CGPoint sPoints[4];//坐标点
sPoints[0] =CGPointMake(100, 220);//坐标1
sPoints[1] =CGPointMake(130, 220);//坐标2
sPoints[2] =CGPointMake(130, 160);//坐标3
sPoints[3] =CGPointMake(230, 160);//坐标4
CGContextAddLines(context, sPoints, 4);//添加线
CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径
参数需要一个CGPoint数组和一个点的数量,需要小于等于数组长度
CGContextAddLines
- 绘制矩形CGContextAddRect
CGContextAddRect(context, CGRectMake(200, 100, 100, 150));
CGContextDrawPath(context, kCGPathStroke);
绘制一个矩形,与currentPoint无关.
绘制一组矩形CGContextAddRects.
CGRect sRects[4];
sRects[0] = CGRectMake(100, 100, 30, 30);
sRects[1] = CGRectMake(100, 130, 30, 30);
sRects[2] = CGRectMake(100, 160, 30, 30);
sRects[3] = CGRectMake(100, 190, 30, 30);
CGContextAddRects(context, sRects, 4);
CGContextDrawPath(context, kCGPathStroke);
CGContextAddRects
- 绘制椭圆CGContextAddEllipseInRect
CGContextAddEllipseInRect(context, CGRectMake(100, 100, 300, 120));
CGContextDrawPath(context, kCGPathStroke);
画一个内切于给定矩形的椭圆
CGContextAddEllipseInRect
- 封闭路径 CGContextClosePath
这个方法会把之前绘制的路径从开始点到结束点封闭起来
CGPoint sPoints[4];//坐标点
sPoints[0] =CGPointMake(100, 220);//坐标1
sPoints[1] =CGPointMake(130, 220);//坐标2
sPoints[2] =CGPointMake(130, 160);//坐标3
sPoints[3] =CGPointMake(230, 160);//坐标4
CGContextAddLines(context, sPoints, 4);//添加线
CGContextClosePath(context);//封闭路径
CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径
CGContextClosePath
- 重新开始路径CGContextBeginPath
这个方法会重新开始一段路径的绘制,会重设各种属性,并且接下来需要从moveToPoint开始,并且CGContextClosePath也不会把之前的路径和CGContextBeginPath之后的路径连接起来,当需要绘制多个图形的时候可以使用.
3.填充路径
- 清除上下文指定范围的内容CGContextClearRect
CGContextClearRect(context, CGRectMake(0, 0, 150, 300));
self.view.backgroundColor = UIColor.whiteColor;
DrawView *view = [[DrawView alloc]initWithFrame:[UIScreen mainScreen].bounds];
view.backgroundColor = [[UIColor alloc]initWithRed:0.0 green:1.0 blue:1.0 alpha:0.99];
[self.view addSubview:view];
CGContextClearRect的效果取决于view是否不透明,这里透明度设置为.99,左上角被切除,露出下面的vc,如果透明度是1,则左上角会变成黑色.
清除左上角
黑色
- 绘制 CGContextDrawPath
前面一直在用
void CGContextDrawPath(CGContextRef c, CGPathDrawingMode mode);
typedef CF_ENUM (int32_t, CGPathDrawingMode) {
kCGPathFill,
kCGPathEOFill,
kCGPathStroke,
kCGPathFillStroke,
kCGPathEOFillStroke
};
CGContextAddEllipseInRect(context, CGRectMake(100, 100, 300, 120));
CGContextAddEllipseInRect(context, CGRectMake(120, 120, 200, 80));
CGContextAddEllipseInRect(context, CGRectMake(140, 140, 150, 50));
CGContextDrawPath(context, kCGPathEOFill);
这个mode主要是两种策略,填充和描边
填充又有直接填充和奇偶规则两种
奇偶规则可以理解为,有一个向量横穿已经构建好的路径,遇到的第一个边为1,第二个边为2,那么1和2之间填充,再遇到3,2和3不填充,再遇到4,3和4填充
奇偶规则
- 类似功能的api
CGContextFillPath; CGContextEOFillPath; CGContextFillRect; CGContextFillRects; CGContextStrokePath;等一系列方法实现的效果和CGContextDrawPath+mode相同,不同的是这些方法在绘制之后会清除路径.
4.设置绘制的样式属性
-
CGContextSetLineWidth 设置线宽
-
CGContextSetLineCap 设置线末端样式
typedef CF_ENUM(int32_t, CGLineCap) {
kCGLineCapButt, //方形
kCGLineCapRound, //圆弧
kCGLineCapSquare //方形并且终点延伸线宽的一半距离
}; -
CGContextSetLineJoin 连接点样式
typedef CF_ENUM(int32_t, CGLineJoin) {
kCGLineJoinMiter, //尖锐的
kCGLineJoinRound, //方形的
kCGLineJoinBevel //圆角
}; -
CGContextSetLineDash 绘制虚线
void CGContextSetLineDash(CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count);
CGFloat lengths[4] = {10.0,5.0,15.0,5.0};
CGContextSetLineDash(context, 0, lengths, 4);
phase表示第一段虚线从第几个点开始
lengths表示虚线的规则,比如{10,10}就是画10个点,跳过10个点
count是lengths的长度
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(context, 100, 100); //画笔移动到(100,100)
CGContextAddLineToPoint(context, 200, 100); //向(200,100)画一条线
CGContextAddLineToPoint(context, 150, 150);
CGContextSetLineWidth(context, 4);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);
CGFloat lengths[4] = {10.0,5.0,15.0,5.0};
CGContextSetLineDash(context, 0, lengths, 4);
CGContextDrawPath(context, kCGPathStroke);
各种样式
- CGContextSetMiterLimit 用来更细致的处理kCGLineJoinMiter
- CGContextSetAlpha 设置绘图透明度
- CGContextSetFillColorWithColor 设置填充颜色,需要一个CGColor,另外还有CGContextSetRGBFillColor ,设置填充颜色,需要RGB值和alpha,同理CGContextSetRGBStrokeColor和CGContextSetStrokeColorWithColor
- CGContextSetShadowWithColor 设置阴影,需要CGSize 和 CGColor
5.路径信息
- CGContextGetPathCurrentPoint //获取current point
- CGContextGetPathBoundingBox //获取包裹上下文路径的最小矩形
- CGContextPathContainsPoint //判断一个点是否在绘制的路径内,需要提供一个mode,因为stoke和fill情况不一样
CGContextDrawPath调用之后,current point就没了,如果需要继续绘制,就需要设置新的current point
CGContextMoveToPoint(context, 100, 200);
CGContextAddLineToPoint(context, 100, 100);
CGContextAddLineToPoint(context, 200, 100);
CGContextClosePath(context);
CGContextSetFillColorWithColor(context, UIColor.blackColor.CGColor);
CGContextDrawPath(context, kCGPathFill);
CGContextMoveToPoint(context, 200, 100);
CGContextAddLineToPoint(context, 200, 200);
CGContextAddLineToPoint(context, 100, 200);
CGContextClosePath(context);
CGContextSetFillColorWithColor(context, UIColor.whiteColor.CGColor);
CGContextDrawPath(context, kCGPathFill);
两个三角形
6.绘制图片
- CGContextDrawImage //需要一个CGRect和一个CGImageRef,这个方法绘制的图像会铺满贴合矩形边缘,产生形变
7.保存和重置上下文的设置
在绘制一个图形时,调用了一堆ContextSetxxx API之后,想要恢复context的设置,然后绘制下一个图形
- CGContextSaveGState 保存上下文的状态
- CGContextRestoreGState 恢复之前保存的上下文的状态
save函数只能保存一个状态
CGContextRef context = UIGraphicsGetCurrentContext(); //获取
CGContextSaveGState(context); //保存初始状态
/* 进行各种设置 */
/* CGContextDrawPath等绘图函数 */
CGContextRestoreGState(context); //恢复初始
/* 可以重新设置 */
/* CGContextDrawPath等绘图函数 */
8.清除上下文
- 这里说的其实是清除已经绘制的图形,清除其实和重绘是一个道理,都是调用setNeedsDisplay让uiview再走一遍drawRect方法,只不过需要清除的时候,drawRect里什么都不写,所以类似这么去清除.
- (void)drawRect:(CGRect)rect{
if(!self.shouldClear){
//绘图
}
}
//清除
self.shouldClear = YES;
[self setNeedsDisplay];
9.内存管理
Core Graphics的内存管理是创建和加减引用计数,Create相关的函数,会创建,retain相关的函数增加引用,Release相关的方法会减计数,计数为0会被释放.
- 创建函数,CGContextCreate,CGImageCreate,CGColorCreate等等
- 减少引用计数 CGContextRelease等
- 增加引用计数 CGContextRetain等
也就是说本文使用的UIGraphicsGetCurrentContext并没有创建CGContextRef 对象,这个对象是layer创建并持有的,因此不需要去release.