iOS Quartz 2D绘图知识详解

** Quartz**

需要注意的是在UIImageView的子类中重写drawRect:是非法的,你不能把自己的绘制和UIImageView进行结合。

**1. ** Mac OS X的Darwin核心之上的绘图层,有时候也认为是CoreGraphics。共有两种部分组成Quartz:
**2. ** Quartz Compositor,合成视窗系统,管理和合成幕后视窗影像来建立Mac OS X使用者接口。
3. Quartz 2D,是iOS和Mac OS X环境下的二维绘图引擎。
涉及内容包括:基于路径的绘图,透明度绘图,遮盖,阴影,透明层,颜色管理,防锯齿渲染,生成PDF,以及PDF元数据相关处理。


****一、Quartz 2D的简单介绍*****
**1. ** Quartz 2D属于Core Graphics(所以大多数相关方法的都是以CG开头),是iOS/Mac OSX 提供的在内核之上的强大的2D绘图引擎,并且这个绘图引擎是设备无关的。也就是说,不用关心设备的大小,设备的分辨率,只要利用Quartz 2D,这些设备相关的会自动处理。
2.Quartz 2D能够提供的强大功能如下:

1. 透明层(transparency layers) 
2. 阴影 基于path的绘图(path-based drawing)
3. 离屏渲染(offscreen rendering) 
4. 复杂的颜色处理(advanced color management) 
5. 抗锯齿渲染(anti-aliased rendering)
6. PDF创建,展示,解析(这部分不在这个系列之中) 配合Core Animation, OpenGL ES,UIKit完成复杂的功能 画板-The Graphics Context
7. 而Quartz 2D的容器就是CGContextRef数据模型。这种数据模型是C的结构体,存储了渲染到屏幕上需要的一切信息。

二、Quartz 2D详解:

Quartz 2D的基本数据类型:
Quartz 2D中的数据类型都是透明的,也就是说用户只需要使用即可,不需要实际访问其中的变量。具体的数据类型包括

1. CGPathRef 路径类型,用来绘制路径(注意带有ref后缀的一般都是绘制的画板)
2. CGImageRef,绘制bitmap
3. CGLayerRef,绘制layer,layer可复用,可离屏渲染
4. CGPatternRef,重复绘制
5. CGShadingRef和CGGradientRef,绘制渐变(例如颜色渐变)
6. CGFunctionRef,定义回调函数,CGShadingRef和CGGradientRef的辅助类型
7. CGColorRef and CGColorSpaceRef,定义如何处理颜色
8. CGFontRef,绘制文字

Quartz 2D的坐标

UIKit默认的坐标系统与Quartz不同。在UIKit中,原点位于左上角,y轴正方向为向下。UIView通过将修改Quartz的Graphics Context的CTM[原点平移到左下角,同时将y轴反转(y值乘以-1)]以使其与UIView匹配。这些都是系统自动帮我们完成。

三、直线/矩形

  1. 基本图形绘制需要的属性
1.获取当前上下文(context)(UIGraphicsGetCurrentContext)
2.设置颜色:
    CGContextSetFillColorWithColor:设置描边颜色
    CGContextSetFillColorWithColor:设置填充颜色
3. 画的范围
    CGContextStrokeRect:描边的范围
    CGContextFillRect:填充的范围
4.CGContextSetLineWidth:线宽
5.CGContextSetLineCap:线顶端的样式
6.CGContextSetLineJoin:线拐角的样式
7. 线的起始点:
    CGContextMoveToPoint:起点 
    CGContextAddLineToPoint:终点
8.CGContextFillPath :填充的路径
9.CGContextStrokePath:描边的路径

直线、矩形 demo

- (void)drawRect:(CGRect)rect {
    
    //1.获得当前context
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //设置颜色 (填充色和 描边的颜色)
    CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:0.8 alpha:1].CGColor);
    CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
    
    //设置描边线宽
    CGContextSetLineWidth(context, 20);
    
    //对矩形进行填色  或  描边
    //(注意:如果先描边再填充,由于矩形大小一致,那么描边的线就会被填充的矩形挡住)
    CGContextFillRect(context, rect);
    CGContextStrokeRect(context, rect);
    
    //-----------------------------------------------------------------
    
    //MARK: ------ 实际line和point的代码
    // 设置描边颜色
    CGContextSetStrokeColorWithColor(context, [UIColor yellowColor].CGColor);    CGContextSetLineWidth(context, 8.0);//线的宽度
    CGContextSetLineCap(context, kCGLineCapRound);//线的顶端
    CGContextSetLineJoin(context, kCGLineJoinRound);//线相交的模式
    
    //-----------------------------------------------------------------
    //MARK:黄色的  ">" 图形
    //移动画笔到哪个点
    CGContextMoveToPoint(context,20,20);
    //画笔画到哪个点
    CGContextAddLineToPoint(context, rect.size.width - 20, rect.size.height / 2 - 20);
    CGContextAddLineToPoint(context, 20, rect.size.height - 20);
    //根据上下文中的点,成线进行描边
    CGContextStrokePath(context);
    
    //------------------------------------------------------------------
    //MARK: 红色的小的三角的填充
    CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
    CGContextMoveToPoint(context, 0, rect.size.height / 2 - 30);
    CGContextAddLineToPoint(context, 30, rect.size.height / 2);
    CGContextAddLineToPoint(context, 0, rect.size.height / 2 + 30);
    CGContextFillPath(context);
   //虚线效果
  //CGContextSetLineDash(context, 1, lengths, 1);

 //------------------------------------------------------------------
    //MARK: 红色虚线效果
    CGContextSetStrokeColorWithColor(context,[UIColor redColor].CGColor);
    CGContextSetLineWidth(context, 1);
    
    CGContextMoveToPoint(context, rect.size.width - 20, 20);
    CGContextAddLineToPoint(context, rect.size.height - 20, rect.size.width - 20);
    CGFloat lengths[] = {20};
    CGContextSetLineDash(context, 1, lengths, 1);
    CGContextStrokePath(context);
}

运行之后的效果:

虚线效果

CGContextSetLineDash参数详解
void CGContextSetLineDash (
CGContextRef _Nullable c,
CGFloat phase,
const CGFloat * _Nullable lengths,
size_t count
);
c 绘制的context,这个不用多说
phase,第一个虚线段从哪里开始,例如传入3,则从第三个单位开始
lengths,一个C数组,表示绘制部分和空白部分的分配。例如传入[2,2],则绘制2个单位,然后空白两个单位,以此重复
count lengths的数量


四、曲线— 圆弧的绘制

Quartz提供了两个方法来绘制圆弧

  1. CGContextAddArc,普通的圆弧一部分(以某圆心,某半径,某弧度的圆弧)

  2. CGContextAddArcToPoint,用来绘制圆角

  3. CGContextAddArc

  1. 结构:
    void CGContextAddArc (
    CGContextRef _Nullable c,
    CGFloat x, // 圆心X坐标
    CGFloat y, // 圆心Y坐标
    CGFloat radius, // 弧度半径
    CGFloat startAngle, // 开始的弧度
    CGFloat endAngle, // 结束的弧度
    int clockwise //1表示顺时针,0表示逆时针
    );
- (void)drawRect:(CGRect)rect {
    //--------------------------------------------------------------------
    //MARK: 画弧
    //1.获取图片上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    //2.设置弧度及位置
     //根据中心点,半径,起始的弧度,最后的弧度,是否顺时针画一个圆弧
    CGContextAddArc(context, rect.size.width / 2, rect.size.height / 2, 20, M_PI_4, M_PI, 1);
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    //3.画
    CGContextDrawPath(context, kCGPathStroke);

    // -----------------------------------------------------

    //MARK:画有线圈的圆饼
    CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);//设置线的颜色
    CGContextSetRGBFillColor(context, 0, 0, 1, 1);//设置填充颜色
    CGContextSetLineWidth(context, 2); //设置线的宽度
    CGContextAddEllipseInRect(context, CGRectMake(10, 30, 60, 60)); //画一个椭圆或者圆
    CGContextDrawPath(context, kCGPathFillStroke);
}

2.CGContextAddArcToPoint

void CGContextAddArcToPoint (
CGContextRef _Nullable c,
CGFloat x1,
CGFloat y1,
CGFloat x2,
CGFloat y2,
CGFloat radius
);
c context x1,y1和当前点(x0,y0)决定了第一条切线(x0,y0)->(x1,y1) x2,y2和(x1,y1)决定了第二条切线 radius,想切的半径。
也就是说,
绘制一个半径为radius的圆弧,和上述 两条直线都相切。

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetRGBStrokeColor(ctx, 1, 0, 0, 1);//设置线的颜色
    CGContextSetRGBFillColor(ctx, 0, 0, 1, 1);//设置填充颜色
    CGContextSetLineWidth(ctx, 2); //设置线的宽度

    //CGContextAddArcToPoint 先要确定三个点,
    //1.从哪里开始划线 CGContextMoveToPoint (也就是两条线的交点)
    //2.第二个点与起始点  确定一条直线
    //3.第三个点与第二个点  确定另外一条直线
    
    //画一个圆角矩形
    //确定矩形的位置和大小
    CGRect rrect = CGRectMake(rect.size.width / 2 - 30, rect.size.height / 2 - 30, 60.0, 60.0);
    
    CGFloat radius = 15.0;//半径,半径为正方形一半时,那就可以切成圆形
    
    CGFloat
    minx = CGRectGetMinX(rrect),//矩形中最小的x
    midx = CGRectGetMidX(rrect),//矩形中最大x值的一半
    maxx = CGRectGetMaxX(rrect);//矩形中最大的x值
    
    CGFloat
    miny = CGRectGetMinY(rrect),//矩形中最小的Y值
    midy = CGRectGetMidY(rrect),//矩形中最大Y值的一半
    maxy = CGRectGetMaxY(rrect);//矩形中最大的Y值
    
    
    CGContextMoveToPoint(ctx, minx, midy);//从点A 开始
    //从点A到点B再从点B到点C形成夹角进行切圆
    CGContextAddArcToPoint(ctx, minx, miny, midx, miny, radius);
    CGContextAddArcToPoint(ctx, maxx, miny, maxx, midy, radius);
    CGContextAddArcToPoint(ctx, maxx, maxy, midx, maxy, radius);
    CGContextAddArcToPoint(ctx, minx, maxy, minx, midy, radius);
    CGContextClosePath(ctx);
    CGContextDrawPath(ctx, kCGPathFillStroke);
    //如果想要进行裁切的话去掉CGContextDrawPath(ctx, kCGPathFillStroke);方法 添加以下方法
    //CGContextClip(context);
    //CGContextFillPath(context);
//添加图片
//CGContextDrawImage(context, rect, self.image.CGImage);    
//或者[self.image drawInRect:rect];
}

运行效果:

贝塞尔曲线

- (void)drawRect:(CGRect)rect {
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextSetLineWidth(context, 4);
   
    
    CGFloat
    minx = CGRectGetMinX(rect),//矩形中最小的x
    midx = CGRectGetMidX(rect),//矩形中最大x值的一半
    maxx = CGRectGetMaxX(rect);//矩形中最大的x值
    
    CGFloat
    miny = CGRectGetMinY(rect),//矩形中最小的Y值
    midy = CGRectGetMidY(rect),//矩形中最大Y值的一半
    maxy = CGRectGetMaxY(rect);//矩形中最大的Y值
    
    //贝塞尔曲线一,两个控制点 红色
    CGPoint s = CGPointMake(minx + 10, miny + 10); //起始点
    CGPoint e = CGPointMake(maxx - 10, maxy - 10);//终点
    CGPoint cp1 = CGPointMake(miny, midy);//控制点1
    CGPoint cp2 = CGPointMake(midy, minx);//控制点2
    CGContextMoveToPoint(context, s.x, s.y);
    CGContextAddCurveToPoint(context, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);
    CGContextStrokePath(context);
    
    //贝塞尔曲线二,一个控制点 蓝色
    CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
    s = CGPointMake(minx, maxy);
    e = CGPointMake(maxx, maxy);
    cp1 = CGPointMake(midx, midy);
    CGContextMoveToPoint(context, s.x, s.y);
    CGContextAddQuadCurveToPoint(context, cp1.x, cp1.y, e.x, e.y);
    CGContextStrokePath(context);
}

运行效果

五、颜色渐变
demo

- (void)drawRect:(CGRect)rect {
    // Drawing code
    
    // 创建Quartz上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 创建色彩空间对象
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    
    // 创建起点颜色
    CGColorRef beginColor = CGColorCreate(colorSpaceRef, (CGFloat[]){0.01f, 0.5f, 0.01f, 1.0f});
    
    // 创建终点颜色
    CGColorRef endColor = CGColorCreate(colorSpaceRef, (CGFloat[]){0.99f, 0.99f, 0.01f, 1.0f});
    
    // 创建颜色数组
    const void **values = (const void*[]){beginColor, endColor};//颜色数组
    CFArrayRef colorArray = CFArrayCreate(
                                          kCFAllocatorDefault,
                                          values,//颜色数组
                                          2,//数组的个数
                                          nil// CGGradientCreateWithColors的最后一个locations参数可以传空,这样默认为从0.0到1.0。
                                          );
    
    // 创建渐变对象
    CGGradientRef gradientRef = CGGradientCreateWithColors(colorSpaceRef, colorArray, (CGFloat[]){
        0.0f,       // 对应起点颜色位置
        1.0f        // 对应终点颜色位置
    });
    
    // 释放颜色数组
    CFRelease(colorArray);
    
    // 释放起点和终点颜色
    CGColorRelease(beginColor);
    CGColorRelease(endColor);
    
    // 释放色彩空间
    CGColorSpaceRelease(colorSpaceRef);
    
    /*
     1.context          上线文
     2.gradientRef      颜色数组
     3.startPoint       开始位置
     4.endPoint         结束位置
     5.CGGradientDrawingOptions         
     当你的起点或者终点不在图形上下文的边缘内时,指定该如何处理。你可以使用你的开始或结束颜色来填充渐变以外的空间。此参数为以下值之一:
     KCGGradientDrawsAfterEndLocation扩展整个渐变到渐变的终点之后的所有点 
     KCGGradientDrawsBeforeStartLocation扩展整个渐变到渐变的起点之前的所有点。
     0不扩展该渐变。
     */
    CGPoint startPoint = CGPointMake(0.0f, 0.0f);
    CGPoint endPoint = CGPointMake(rect.size.width, rect.size.height);
    CGGradientDrawingOptions options = kCGGradientDrawsAfterEndLocation;
    CGContextDrawLinearGradient(
                                context,
                                gradientRef,
                                startPoint,
                                endPoint,
                                kCGGradientDrawsBeforeStartLocation
//                                kCGGradientDrawsAfterEndLocation
                                );
    
    
    // 释放渐变对象
    CGGradientRelease(gradientRef);
}

效果图


六、多中颜色的渲染
demo

- (void)drawRect:(CGRect)rect {
    // 创建Quartz上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 创建色彩空间对象
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    
    // 创建渐变对象
    CGGradientRef gradientRef =
    CGGradientCreateWithColorComponents(colorSpaceRef,
                                        (CGFloat[]){
                                         1.0f,0.8f,0.5f,1.0f,//第一个颜色RGB 和透明度
                                         0.6f,0.5f,0.6f,1.0f,//第二个颜色RGB 和透明度
                                         0.3f,0.2f,0.f,1.0f,//第三个颜色RGB 和透明度
                                         .0f,0.0f,0.3f,1.0f
                                        },
                                        (CGFloat[]){  0.0f,0.3f,.6f,1},//颜色渐变的位置
                                        4);//颜色的个数
    
    // 释放色彩空间
    CGColorSpaceRelease(colorSpaceRef);
    
    // 填充渐变色
    CGContextDrawLinearGradient(context, gradientRef, CGPointMake(0.0f, 0.0f), CGPointMake(320.0f, 460.0f), 0);
    
    // 释放渐变对象
    CGGradientRelease(gradientRef);
}

效果


此外还可以绘制文字

//绘制文字(不用开启图形上下文)
    NSString *str = @“hehe呵呵hehe🙄";
    CGRect wordRect = CGRectMake(100, 100, 100, 40);
    //给要显示的文字设置一个范围
    //NSForegroundColorAttributeName:文字颜色
    //NSFontAttributeName : 字体
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    //设置字体颜色
    dict[NSForegroundColorAttributeName] = [UIColor greenColor];
    //设置字体大小
    dict[NSFontAttributeName] = [UIFont systemFontOfSize:20];
    [str drawInRect:wordRect withAttributes:dict];

还可以截屏操作

 // 1.开启上下文
    UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0.0);
    
    // 2.将控制器view的layer渲染到上下文
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    
    // 3.取出图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 4.结束上下文
    UIGraphicsEndImageContext();

几何图形绘制属性---详解
1.Graphics Context(图层上下文)

  1. Graphics Context
    其实就是表示了一个绘制目标,也就是你打算绘制的地方,它包含绘制系统用于完成绘制指令的绘制参数和设备相关信息。Graphics Context定义了基本的绘制属性,如颜色、裁减区域、线条宽度和样式信息、字体信息、混合模式等。
  1. *获取Graphics Context:
    1. Quartz提供的创建函数、Mac OS X框架或IOS的UIKit框架提供的函数。Quartz提供了多种Graphics Context的创建函数,包括bitmap和PDF,我们可以使用这些Graphics Context创建自定义的内容。
    2. 在代码中,我们用CGContextRef来表示一个Graphics Context。当获得一个Graphics Context后,可以使用Quartz 2D函数在上下文(context)中进行绘制、完成操作(如平移)、修改图形状态参数(如线宽和填充颜色)等。

查阅的资料
感谢大家在博客或者简书的分享,我在这里做了总结和扩展,仅供大家学习和讨论,如果有什么不对的请大家及时留言评论,谢谢~
本人github账号: 702029772@qq.com

  1. 博主:zenny_chen http://www.cnblogs.com/zenny-chen/archive/2012/02/23/2364152.html
  1. 博主:贱见 https://account.aliyun.com/login/login.htm?from_type=yqclub&oauth_callback=https%3A%2F%2Fyq.aliyun.com%2Fusers%2F1482959332945947%3Fspm%3D5176.100239.blogrightarea35932.3.0TK01t%26do%3Dlogin
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容