Quartz 2D绘图

简述

Quartz 2D的API是纯C语言的,它是一个二维绘图引擎,同时支持iOS和Mac系统。Quartz2D的API来自于Core Graphics框架,数据类型和函数基本都以CG作为前缀。通常,我们可以使用系统提供的控件去完成大部分UI,但是有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,类似自定义控件。其实,iOS中大部分控件的内容都是通过Quartz2D画出来的,因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)。

Quartz 2D 功能

  • 绘制图形 : 线条\三角形\矩形\圆\弧等
  • 绘制文字
  • 绘制\生成图片(图像)
  • 读取\生成PDF
  • 截图\裁剪图片
  • 自定义UI控件
  • … …

图形上下文 (Graphics Context)

翻译自官方文档

图形上下文表示一个绘图目的地。它包含绘图参数和绘图系统执行任何后续绘图命令所需的所有设备特定信息。图形上下文定义了基本的绘图属性,例如绘图时使用的颜色,剪裁区域,线宽和样式信息,字体信息,合成选项等等。

您可以通过使用Quartz上下文创建功能或通过使用由iOS OS中的某个Mac OS X框架或UIKit框架提供的更高级功能来获取图形上下文。Quartz提供各种风格的Quartz图形上下文的功能,包括位图和PDF,您可以使用它来创建自定义内容。

图形上下文在代码中由数据类型表示CGContextRef,这是一种不透明的数据类型。获取图形上下文后,可以使用Quartz 2D函数绘制上下文,在上下文上执行操作(例如转换),并更改图形状态参数(如线宽和填充颜色)。

Quartz2D提供的Graphics Context类型:

  • Bitmap Graphics Context

  • PDF Graphics Context

  • Window Graphics Context

  • Layer Graphics Context

  • Printer Graphics Context

Quartz 2D自定义View

1、Quartz2D自定义view

首先,得有图形上下文,因为它能保存绘图信息,并且决定着绘制到什么地方去。

其次,那个图形上下文必须跟view相关联,才能将内容绘制到view上面。

自定义view的步骤:
(1)新建一个类,继承自UIView

(2)实现 -(void)drawRect:(CGRect)rect 方法,然后在这个方法中做以下操作:

(a)取得跟当前view相关联的图形上下文

  CGContextRef ctx = UIGraphicsGetCurrentContext();

(b)绘制相应的图形内容

Demo

1. 绘制基本线段

- (void)drawRect:(CGRect)rect {
    //绘制直线
    [self drewSegment];
    
    //绘制曲线
    [self drewCurve];
}

//绘制简单线段
-(void)drewSegment{
    //获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    //绘制
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(50, 50)];
    [path addLineToPoint:CGPointMake(150, 300)];
    
    //线条宽度
    CGContextSetLineWidth(context, 5);
    
    //线条颜色
    [[UIColor cyanColor] set];
    
    //添加到上下文
    CGContextAddPath(context, path.CGPath);
    //渲染到view上
    CGContextStrokePath(context);
}

//绘制曲线
-(void)drewCurve{
    //获取上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    //绘制曲线
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(20, 300)];
    [path addQuadCurveToPoint:CGPointMake(200, 300) controlPoint:CGPointMake(50, 50)];
    //线条宽度
    CGContextSetLineWidth(context, 10);
    //线条颜色
    [[UIColor cyanColor] set];
    //添加到上下文
    CGContextAddPath(context, path.CGPath);
    //渲染到view上
    CGContextStrokePath(context);
}
直线.png

曲线.png

2. 绘制基本图形

-(void)drawRect:(CGRect)rect{
    //画矩形
//    [self drawRectangle];
    
    //画圆角矩形
//    [self drawRoundedRect];
    
    //画椭圆
//    [self drawOval];
    
    //画弧
//    [self drawArc:rect];
    
    //画扇形
    [self drawSector:rect];

}

//画矩形
-(void)drawRectangle{
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 100)];
    CGContextAddPath(context, path.CGPath);
    CGContextStrokePath(context);
}

//画圆角矩形
-(void)drawRoundedRect{
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) cornerRadius:30];
    //以下方法底层会调用上面的绘制方式
    [path stroke];
}

//画椭圆
-(void)drawOval{
    //椭圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 200, 200, 80)];
    //圆
//    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 150, 100, 100)];
    [path stroke];
}

//画弧
-(void)drawArc:(CGRect)rect{
    //圆点
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    //半径
    CGFloat radius = rect.size.width * 0.5 - 10;
    /**
     Description
     @param CGPoint 圆心
            radius 半径
            startAngle 起始角度
            endAngle 终止角度
            clockwise YES=顺时针 NO=逆时针
     */
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];
    
    [path stroke];
}

//画扇形
-(void)drawSector:(CGRect)rect{
    //圆点
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    //半径
    CGFloat radius = rect.size.width * 0.5 - 10;
    
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];
    
    //闭合曲线
    [path addLineToPoint:center];
//    [path closePath];
//
//    [path stroke];
    
    //用fill的话自动闭合曲线
    [path fill];
    
}
矩形.png
圆角矩形.png
椭圆.png
弧.png
扇形.png

3. 绘制饼图

-(void)drawRect:(CGRect)rect{
    //数据
    NSArray *dataArray = @[@33,@33,@33];
    
    //绘图信息
    CGPoint center = CGPointMake(rect.size.width * 0.5, rect.size.height * 0.5);
    CGFloat radius = rect.size.width * 0.5 - 10;
    CGFloat startAngle = 0;
    CGFloat angle = 0;
    CGFloat endAngle = 0;
    for (NSNumber *num in dataArray ) {
        startAngle = endAngle;
        angle = num.intValue/99.0 * 2 * M_PI;
        endAngle = startAngle + angle;
        UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
        [[self randomColor] set];
        
        [path addLineToPoint:center];
        [path fill];
    }
}

-(void)reDraw{
    //重绘
    [self setNeedsDisplay];
}

//随机颜色
-(UIColor *)randomColor{
    CGFloat r = arc4random_uniform(256)/255.0;
    CGFloat g = arc4random_uniform(256)/255.0;
    CGFloat b = arc4random_uniform(256)/255.0;

    return [UIColor colorWithRed:r green:g blue:b alpha:1.0];
}
饼图.png

4. 绘制带水印图

使用图片上下文,并不是在View上绘制

- (void)viewDidLoad {
    [super viewDidLoad];
    //加载图片
    UIImage *image = [UIImage imageNamed:@"Logo"];
    //开启上下文 参数2:不透明度
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    //绘制图片到上下文
    [image drawAtPoint:CGPointMake(0, 0)];
    //绘制文字到上下文
    NSString *str =@"Kinken_Yuen";
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    dict[NSFontAttributeName] = [UIFont systemFontOfSize:30];
    dict[NSForegroundColorAttributeName] = [UIColor whiteColor];
    [str drawAtPoint:CGPointMake(450, 550) withAttributes:dict];
    //从上下文获取新的图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    //关闭上下文
    UIGraphicsEndImageContext();
    //显示
    self.imageView.image = newImage;
}

上下文大小是图片大小640x629,添加文字时的坐标点所以比较大

水印.png

5. 圆形裁剪图片

- (void)viewDidLoad {
    [super viewDidLoad];
    //加载图片
    UIImage *image = [UIImage imageNamed:@"logo"];
    //开启图片上下文
    UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
    //添加圆形裁剪区域
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
    [path addClip];
    //将图片绘制到上下文中(超出裁剪区域不显示)
    [image drawAtPoint:CGPointZero];
    //从上下文取出图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    //关闭上下文
    UIGraphicsEndImageContext();
    //显示图片
    self.imageView.image = newImage;
}
圆形裁剪.png

6. 圆形裁剪带边框图片

@implementation UIImage (ClipImage)
+ (UIImage *)clipImageWithBorder:(UIImage *)image withColor:(UIColor *)color borderWith:(CGFloat)width{
    //图片上下文尺寸
    CGSize size = CGSizeMake(image.size.width + 2 * width, image.size.height + 2 * width);
    
    //开启图片上下文
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    
    //添加大圆边框
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, size.width, size.height)];
    [color set];
    [path fill];
    
    //添加圆形裁剪区域
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(width, width, image.size.width, image.size.height)];
    [clipPath addClip];
    
    //图片绘制到上下文
    [image drawAtPoint:CGPointMake(width, width)];
    
    //拿到照片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    //关闭上下文
    UIGraphicsEndImageContext();
    
    return newImage;
}
@end
- (void)viewDidLoad {
    [super viewDidLoad];
    UIImage *image = [UIImage imageNamed:@"logo"];
    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage clipImageWithBorder:image withColor:[UIColor cyanColor] borderWith:5]];
    imageView.frame = CGRectMake(80, 20, image.size.width, image.size.height);
    [self.view addSubview:imageView];
}
屏幕快照 2018-06-07 上午9.52.09.png

7. 简单截屏

- (IBAction)screenShot {
    //开启view大小的图形上下文
    UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0);
    
    //用绘制方式不能将view的layer显示到上下文,必须用渲染方式
//    [self.view.layer drawInContext:UIGraphicsGetCurrentContext()];
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
    
    //图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    //将图片转成二进制数据保存到桌面
    NSData *data = UIImagePNGRepresentation(newImage);
    [data writeToFile:@"/Users/kinken_yuen/Desktop/logo.png" atomically:YES];
}
logo.png

8. 图片局部截取

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageV;

/*手势起始点*/
@property(nonatomic,assign)CGPoint startP;

/*裁剪用的UIView*/
@property(nonatomic,weak)UIView *clipV;
@end

@implementation ViewController

-(UIView *)clipV{
    if (_clipV == nil) {
        UIView *view = [[UIView alloc] init];
        view.backgroundColor = [UIColor lightGrayColor];
        view.alpha = 0.3;
        _clipV = view;
        //个人感觉把截图的区域添加到imageV更合乎常理
        [self.imageV addSubview:view];
    }
    return _clipV;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.imageV.userInteractionEnabled = YES;
}

- (IBAction)dragGestureOnPicture:(UIPanGestureRecognizer *)sender {
    //记录起始点
    CGPoint cur = [sender locationInView:self.imageV];
    if (sender.state == UIGestureRecognizerStateBegan) {
         self.startP = cur;
    }else if(sender.state == UIGestureRecognizerStateChanged){ //拖拽手势
        CGFloat x = self.startP.x;
        CGFloat y = self.startP.y;
        CGFloat w = [sender locationInView:self.imageV].x - x;
        CGFloat h = [sender locationInView:self.imageV].y - y;
        CGRect rect = CGRectMake(x, y, w, h);
        //裁剪用的UIview
        self.clipV.frame = rect;
    }else if(sender.state ==UIGestureRecognizerStateEnded){
        /*手势结束裁剪图片*/
        //开启imageV大小的图片上下文
        UIGraphicsBeginImageContextWithOptions(self.imageV.frame.size,NO, 0);
        
        //添加裁剪区域,UIView的大小
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.clipV.frame];
        [path addClip];
        
        //渲染到图形上下文之前要把截取区域从imageView上移除
        [self.clipV removeFromSuperview];

        
        //将原始图片渲染到图片上下文,并按裁剪区域截取
        [self.imageV.layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        
        //关闭上下文
        UIGraphicsEndImageContext();
        
        //更新imageView
        self.imageV.image = newImage;
    }
}
@end
原始.png
裁剪区域.png
裁剪.png

注意点

  1. 在drawRect:方法中取得与自定义view相关联的图形上下文
  2. drawRect:调用时机是在view第一次显示到屏幕时
  3. 手动调用drawRect:方法时,系统是不会给你创建跟view相关联的上下文,需要重绘制的时候应该调用setNeedsDisplay方法,此方法是底层需要刷新屏幕时调用drawRect:方法
  4. view内部有一个layer属性,drawRect:方法取得的是Layer Graphics Context,实质是绘制到view的layer上
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • Quartz 2D绘图(CoreGraphics) 1. Quartz概述 Quartz是Mac OS X的Dar...
    北辰青阅读 703评论 0 1
  • Quartz 2D介绍 什么是Quartz2D ?Quartz 2D是⼀个二维绘图引擎,同时支持iOS和Mac系统...
    mojue阅读 828评论 1 6
  • ** Quartz** 需要注意的是在UIImageView的子类中重写drawRect:是非法的,你不能把自己的...
    LiYaoPeng阅读 2,638评论 14 16
  • 90年代,能在中国看到老外,还是件新鲜的玩意儿。那年头资讯不发达,绿眼红鼻子五颜六色头发的外国人,能吸引国...
    晚睡晚起阅读 756评论 3 2
  • 在什么样的环境下,人可以专注做事,或者可以持续思考呢。 安静环境是最好的选择,但是为什么环境必须要安静才行呢。 我...
    天行践阅读 441评论 0 0