Quartz2D

Quartz 2D是一个二维图形绘制引擎,我们可以使用Quartz 2D 实现基本路径的绘制、透明度、描影、绘制阴影、透明层、颜色管理、反锯齿、PDF文档生成和PDF元数据访问。在需要的时候,Quartz 2D还可以借助图形硬件的功能。

一、自定义View

利用Quartz 2D绘制东西到View上,首先要有图形上下文用于保存绘图信息,再把内容绘制到View上。
自定义View的步骤:
1、新建一个继承自UIView的类;
2、实现- (void)drawRect:(CGRect)rect方法,在这个方法中实现自定义;
获取跟当前View想关联的图形上下文
绘制相应的图形内容
利用图形上下文将绘制的所有内容渲染显示到View上面

1
2
3
4
5
6
7
8
9
// 当view第一次显示的时候调用,rect == self.bounds,该方法不能手动调用,只能让系统自动调用
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext(); // 获得图形上下文
CGContextAddRect(ctx, CGRectMake(100, 100, 200, 200)); //绘制矩形
// CGContextSetRGBStrokeColor(ctx, 0, 1, 0, 1.0);// 边界颜色
CGContextSetRGBFillColor(ctx, 0, 1, 0, 1); //填充颜色
// CGContextStrokePath(ctx);// 渲染边界
CGContextFillPath(ctx); // 渲染填充
}

二、基础绘图

第一步:获得图形上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
第二步:设置图形信息、图形状态
第三步:把图形信息渲染到View上
CGContextStrokePath(ctx); 边界
CGContextFillPath(ctx); 填充
CGContextDrawPath(ctx, kCGPathFillStroke); 边界和填充
绘制线条只能以边界形式绘制,不能填充

1、绘制线条

1
2
3
4
5
6
7
8
9
10
11
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 绘制第一条线
CGContextMoveToPoint(ctx, 100, 100); //设置起点
CGContextAddLineToPoint(ctx, 200, 200); //添加一条直线到终点
CGContextSetLineWidth(ctx, 10); // 设置线条宽度
CGContextSetRGBStrokeColor(ctx, 1.0, 0, 0, 1.0); // 设置线条颜色
CGContextSetLineCap(ctx, kCGLineCapRound); // 设置线条两端样式(加帽子)
// 绘制第二条线
CGContextAddLineToPoint(ctx, 300, 50); // 该方法会自动连接上一条线的终点
CGContextSetLineJoin(ctx, kCGLineJoinRound); // 设置线条转折点样式
CGContextStrokePath(ctx);

2、绘制三角形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 第一条线
CGContextMoveToPoint(ctx, 100, 100);
CGContextAddLineToPoint(ctx, 200, 200);
CGContextSetLineWidth(ctx, 10);
CGContextSetRGBStrokeColor(ctx, 1.0, 0, 0, 1.0);
CGContextSetLineCap(ctx, kCGLineCapRound);
// 绘制第二条线
CGContextAddLineToPoint(ctx, 300, 50);
CGContextSetLineJoin(ctx, kCGLineJoinRound);
// 绘制第三条线
// CGContextAddLineToPoint(ctx, 100, 100);// 该方法会自动连接上一条线的终点
CGContextClosePath(ctx);// 该方法会自动连接起点和终点
CGContextStrokePath(ctx);

3、绘制圆、圆弧

1
2
3
4
5
6
7
CGContextRef ctx = UIGraphicsGetCurrentContext();
// CGContextAddEllipseInRect(ctx, CGRectMake(10, 10, 100, 100)); // 圆
CGContextAddEllipseInRect(ctx, CGRectMake(10, 10, 150, 100)); // 椭圆
// [[UIColor yellowColor] set]; // 统一设置颜色
[[UIColor yellowColor] setFill]; // 设置填充颜色
[[UIColor redColor] setStroke]; // 设置边界颜色
CGContextDrawPath(ctx, kCGPathFillStroke);

绘制弧的方法
坐标是以X轴的正坐标方向为角度0开始计算
c 当前图形上下文
x 圆心的x值
y 圆心的y值
radius 半径
startAngle 开始角度
endAngle 结束角度
clockwise 绘制方向:0:表示顺时针,1:表示逆时针
CGContextAddArc(CGContextRef _Nullable c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)

1
2
3
4
5
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat centerX = rect.size.width * 0.5;
CGFloat centerY = rect.size.height * 0.5;
CGContextAddArc(ctx, centerX, centerY, 50, 0 , 2 * M_PI , 0);
CGContextStrokePath(ctx);

4、绘制图片

1
2
3
4
5
6
- (void)drawRect:(CGRect)rect {
UIImage * img = [UIImage imageNamed:@"girl.png"];
[img drawInRect:rect]; // 拉伸填充
}
[img drawAsPatternInRect:rect]; 平铺
[img drawAtPoint:CGPointMake(20, 20)];指定绘制起点坐标

5、绘制文字

1
2
3
4
5
6
7
NSString *str = @"好学若饥,谦卑若愚";
[str drawInRect:CGRectMake(10, 10, 50, 50) withAttributes:nil];//指定文字显示区域
// withAttributes 这个参数可以设置一个富文本的字典
// NSDictionary *dic = @{NSFontAttributeName:[UIFont systemFontOfSize:20],
// NSForegroundColorAttributeName: [UIColor blueColor]};
//[str drawAtPoint:CGPointMake(10, 10) withAttributes:dic];//指定起始位置显示文字

6、图形上下文栈
主要作用:备份保存,相当于还原上下文初始状态,不用手动复原。

当需要连续绘制多个不同样式的图形在一起的时候,在设置当前绘图信息和绘图状态之前,保存一份图形上下文到栈区,在绘制下一个图形时,把刚才保存的那个图形上下文出栈,相当于你得到了上一个图形上下文的初始状态。

图形上下文的保存和出栈都是成对出现的。
CGContextSaveGState(ctx); // 将当前图型上下文保存到一个栈里面
CGContextRestoreGState(ctx); // 将图形上下文出栈

7、矩阵操作
我们知道利用transform属性的旋转、平移、缩放等默认都是以中心点为基准点的。
而在图形上下文中默认是以0,0点作为基准点。

1
2
3
4
5
6
7
8
9
10
11
12
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[UIColor redColor] set];
CGContextAddRect(ctx, CGRectMake(50, 50, 100, 100));//没缩放的红色矩形作为参照物
CGContextStrokePath(ctx);
[[UIColor blueColor] set];
CGContextScaleCTM(ctx, 0.5, 0.5); //矩阵里的缩放
// CGContextTranslateCTM(ctx, 100, 0); // 矩阵里的平移,向X坐标平移100
CGContextAddRect(ctx, CGRectMake(50, 50, 100, 100));//蓝色矩形是经过矩阵操作后的矩形
CGContextStrokePath(ctx);

矩阵操作里的旋转则相对难理解一点。可以通过先平移、再旋转、再上移来达到效果
例如:实现旋转45度
CGContextTranslateCTM(ctx, rect.size.width 0.5, rect.size.height 0.5);
CGContextRotateCTM(ctx, M_PI_4);
CGContextTranslateCTM(ctx, -rect.size.width 0.5, -rect.size.height 0.5);

三、绘图的三种方式

1、直接图形上下文绘制

1
2
3
4
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextFillRect(ctx, CGRectMake(30,30, 200, 200));
}

2、通过创建path,每一个图形对应一个绘图路径,优点是会比较清晰,增加可读性,缺点是代码多一些

1
2
3
4
5
6
7
8
9
10
11
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 拼接路径
CGMutablePathRef path1 = CGPathCreateMutable();
CGPathAddEllipseInRect(path1, NULL, CGRectMake(30,30, 200, 200));
CGContextAddPath(ctx, path1);
[[UIColor redColor] set];
CGContextStrokePath(ctx);
CGPathRelease(path1); // 销毁路径
}

3、通过UIBezierPath绘图

1
2
3
4
5
- (void)drawRect:(CGRect)rect {
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(100, 100, 100, 200)];
[[UIColor redColor] set];
[path fill];
}

四、绘图应用

1、在自定义View内裁剪图片
(1)按照需求设定一个裁剪的封闭图形区域
(2)绘制你要裁剪的图片到这个封闭区域内,超出部分会裁剪调。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)drawRect:(CGRect)rect {
// 1、先画一个封闭区间的圆
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, CGRectMake(30, 200, 200, 200));
// 2、必须在渲染之前裁剪,调用该方式时要求当前上下文已经存在一个封闭的图形区域
CGContextClip(ctx);
// 3、渲染
CGContextStrokePath(ctx);
// 4、加载图片
UIImage *image = [UIImage imageNamed:@"psb.jpeg"];
// 设置裁剪的0,0点
[image drawAtPoint:CGPointZero];
}

按照这个思路你可以裁剪你想要得到的图形图片。

2、脱离- (void)drawRect:(CGRect)rect 方法实现裁剪图片
给UIImage做一个分类,实现常用的裁剪圆形头像的小功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#import "UIImage+Extension.h"
@implementation UIImage (Extension)
+(instancetype)circleImageWithName:(NSString *)imageName{
UIImage *image = [UIImage imageNamed:imageName];
UIGraphicsBeginImageContext(image.size);// 创建图形上下文
CGContextRef ctx =UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, image.size.width, image.size.height)); // 绘制圆
CGContextClip(ctx);
[image drawAtPoint:CGPointZero]; // 绘制图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();// 获得图片
UIGraphicsEndPDFContext(); // 关闭图形上下文
return newImage;
}
+(instancetype)circleImageWithName:(NSString *)imageName borderWidth:(CGFloat)borderWidth borderColor:(UIColor *)borderColor{
UIImage *image = [UIImage imageNamed:imageName];
CGFloat marginX = borderWidth;
CGFloat marginY = marginX;
CGFloat contextW = image.size.width + marginX;
CGFloat contextH = image.size.height + marginY;
// 开启图形上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(contextW, contextH), NO, 0.0);
// 绘制大圆
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(ctx, CGRectMake(0, 0, contextW, contextH));
[borderColor set];
CGContextFillPath(ctx);
// 绘制小圆
CGContextAddEllipseInRect(ctx, CGRectMake(marginX * 0.5, marginY * 0.5, image.size.width, image.size.height));
CGContextClip(ctx);
// 绘制图片
[image drawAtPoint:CGPointMake(marginX * 0.5, marginY * 0.5)];
// 从当前上下文获得图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
return newImage;
}

3、通过赋值属性改变绘制圆的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)awakeFromNib {
[super awakeFromNib];
self.radius = 10;//设置初始圆的半径
}
- (void)setRadius:(CGFloat)radius {
_radius = radius;
[self setNeedsDisplay];//间接调用drawRect方法,重新绘制图画
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat x = rect.size.width * 0.5;
CGFloat y = rect.size.height * 0.5;
CGContextAddArc(ctx,x, y, self.radius, 0, M_PI * 2, 0);
CGContextFillPath(ctx);
}

4、简单的雪花飘落

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateImage)];
link.frameInterval = 2.0; // 60 / 2 =30次
[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
return self;
}
- (void)updateImage{
self.y += 5;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
if (self.y > rect.size.height) {
self.y = 0 ;
}
UIImage *image = [UIImage imageNamed:@"snow"];
[image drawAtPoint:CGPointMake(10, self.y)];
}

5、截屏截图

1
2
3
4
5
6
7
8
9
10
11
12
// 延迟1s截屏
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 1.开启图形上下文
UIGraphicsBeginImageContext(self.view.frame.size);
// 2.将控制器view的内容渲染到上下文中
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
// 3.从图形上下文中获得图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
NSData *imageData = UIImagePNGRepresentation(newImage);
[imageData writeToFile:@"/Users/apple/desktop/abc.png" atomically:YES];
});

6、制作水印图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
UIImage *image = [UIImage imageNamed:@"bgImg.png"];
UIImage *logoImage = [UIImage imageNamed:@"logo.png"];
CGFloat margin = 10;
// 1.开启上下文
UIGraphicsBeginImageContext(CGSizeMake(375, 400));
// 1.1 绘制背景图
[image drawInRect:CGRectMake(0, 0, 375, 400)];
// 1.2 绘制logo
[logoImage drawAtPoint:CGPointMake(self.view.frame.size.width - margin - logoImage.size.width, margin)];
// 1.2 绘制文字
// NSString *str = @"XX公司程序员";
// NSMutableDictionary *dict = [NSMutableDictionary dictionary];
// dict[NSFontAttributeName] = [UIFont systemFontOfSize:20];
// [str drawAtPoint:CGPointMake(151, 100) withAttributes:dict];
// 2.获得图片
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// 3.关闭上下文
UIGraphicsEndImageContext();

打赏支持一下呗!