Quartz 2D学习记录
Quartz 2D简单介绍
一、什么是Quartz 2D
Quarz 2D是一个二维绘画引擎,同时支持ios和mac,其API是Core Graphics框架的,是纯C语言的。IOS系统提供的大部分控件的内容都是通过Quartz 2D画出来的,因此Quartz 2D的一个很重要的价值是:自定义view(自定义UI控件)。
二、一个重要的概念:图形上下文
图形上下文(Graphics context)是一个CGContextRef数据,其作用是:
- 保存绘图信息、绘图属性
- 绘制目标图案
- 输出绘制好的图案到输出目标去,即渲染到什么地方去(可以是PDF文件、bitmap或者显示器的窗口上)
三、自定义view,即绘制view
在view中实现- (void)drawRect:(CGRect)rect方法,然后在方法中:
1. 取得跟当前view相关联的图形上下文;
2. 绘制相应的图形内容
3. 利用图形上下文将绘制好的图形内容渲染显示到view上面
Quartz 2D简单使用
代码示例
//绘制步骤:1、获取当前视图的图形上下文 2、开始绘图 3、渲染绘制内容
/**绘制直线**/
//1、获取当前视图的图形上下文(图形上下文决定绘制的输出目标)
CGContextRef ctx = UIGraphicsGetCurrentContext();
//2、开始绘图
//设置起点
CGContextMoveToPoint(ctx, 20, 50);
//设置终点
CGContextAddLineToPoint(ctx, 300, 50);
//设置线条属性
//设置颜色
CGContextSetStrokeColorWithColor(ctx, [UIColor purpleColor].CGColor);
//另外一种设置颜色的方式
// [[UIColor purpleColor] set];
//设置线条宽度
CGContextSetLineWidth(ctx, 10);
//设置线条起点和终点的样式为圆角
CGContextSetLineCap(ctx, kCGLineCapRound);
//3、将画布上绘制的内容渲染到view的layer上
CGContextStrokePath(ctx);
/**绘制三角形**/
//设置起点
CGContextMoveToPoint(ctx, 150, 80);
//设置第一个拐点
CGContextAddLineToPoint(ctx, 220, 150);
//设置第二个拐点
CGContextAddLineToPoint(ctx, 80, 150);
//设置第三个点(终点)
// CGContextAddLineToPoint(ctx, 150, 80);
//可以用下面方法代替 缝合起点和终点
CGContextClosePath(ctx);
//设置线条的拐点转角样式为圆角
CGContextSetLineJoin(ctx, kCGLineJoinRound);
//渲染
CGContextStrokePath(ctx);
/**绘制空心四边形**/
CGContextAddRect(ctx, CGRectMake(40, 200, 200, 100));
//设置空心(线条)颜色
// CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
//也可以这样设置颜色
[[UIColor lightGrayColor] setStroke];
//设置线条宽度
CGContextSetLineWidth(ctx, 10);
//渲染(空心的)
CGContextStrokePath(ctx);
/**绘制实心四边形**/
CGContextAddRect(ctx, CGRectMake(40, 320, 200, 100));
//设置实心颜色
// CGContextSetFillColorWithColor(ctx, [UIColor orangeColor].CGColor);
[[UIColor orangeColor] setFill];
//渲染(实心的)
CGContextFillPath(ctx);
/**绘制圆(可以用绘制椭圆的方式画圆)**/
//参数依次为圆心x,圆心y,半径,开始位置的弧度,结束位置的弧度,绘制路径(1为顺时针,0为逆时针)
//由于Quartz2D的坐标系是x轴向右,y轴向上,不同于UIKit坐标系。因此在不将Quartz2D坐标系翻转的情况下,画出来的图形是与原图形关于x轴对称的。
CGContextAddArc(ctx, 100, 520, 50, 0, M_PI_2, 1);
CGContextStrokePath(ctx); //空心
// CGContextFillPath(ctx); //实心
/**绘制椭圆**/
CGContextAddEllipseInRect(ctx, CGRectMake(230, 400, 120, 200));
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextStrokePath(ctx);
/**绘制弧线**/
// CGContextAddArcToPoint(ctx, 100, 200, 100, 200, 50);
CGContextAddArc(ctx, 200, 200, 100, 0, M_PI, 1);
CGContextSetStrokeColorWithColor(ctx, [UIColor cyanColor].CGColor);
CGContextStrokePath(ct
绘制结果
图形上下文栈
一、绘制原理
举个例子,假如要绘制两条线,一条红色一条默认的黑色。先绘制红色线,绘制完毕渲染上去后,再去绘制第二条。绘制第二条的时候如果不重新设置绘画颜色,那么绘制出来的线条也是红色的。也就是说,绘制属性如果不对其清空(即重新设置)是默认保留在图形上下文上的。因此可以这样理解:
一个图形上下文有3块区域,分别是绘制属性,图像信息,绘制区域:
- 绘制属性:包括画笔的颜色、线条宽度、是否圆角等等;
- 绘图信息:比如要绘制一条直线,那么保存了线条的起点和终点,绘制圆,保存了半径、中点坐标、起点终点、方向等等。也就是说,绘图信息中保存的是绘图路径,这个在下面会详细介绍。
- 绘图区域:这个区域不是指视图上的区域,而是图形上下文上的绘制区域,因为绘画是在图形上下文上绘制好之后才被渲染到视图的制定区域上的。
二、保存图形上下文绘制属性
前面说过如果要绘制多个不同属性的图形,那么每次渲染好一个图形后就要重新设置绘制属性,通常绘制多个图形都是这样做的。有时候可能用到一个简单的方法:即在绘制一个图形前先保存当前图形上下文中的绘制属性,这个绘制属性会被保存到图形上下文栈上,如果下次需要绘制同样属性的图形,直接把这个绘制属性从栈顶取出来(恢复)就好了。需要注意的是保存一次只能取一次,可以保存多次,但是每次只从栈顶取。
代码示例
/**保存绘制属性(以绘制3条线为例,第一条第三条属性一致)**/
//第一条
CGContextMoveToPoint(ctx, 20, 300);
CGContextAddLineToPoint(ctx, 200,300);
//设置绘制属性
CGContextSetLineWidth(ctx, 10);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor);
CGContextStrokePath(ctx);
//第二条线
//先保存当前的绘制属性
CGContextSaveGState(ctx);
CGContextMoveToPoint(ctx, 20, 400);
CGContextAddLineToPoint(ctx, 200, 400);
//设置新的绘制属性
CGContextSetLineWidth(ctx, 5);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextStrokePath(ctx);
//第三条线
//取出(恢复)之前保存的绘制属性
CGContextRestoreGState(ctx);
CGContextMoveToPoint(ctx, 20, 500);
CGContextAddLineToPoint(ctx, 200, 500);
CGContextStrokePath(ctx);
绘制结果
矩阵操作
一、矩阵操作介绍
矩阵操作主要有旋转操作、缩放操作、平移操作,是以视图左上角为原点进行的。对矩阵的操作一定要在绘制之前完成,不然绘制完了再操作无效。
二、代码说明
CGContextRef ctx = UIGraphicsGetCurrentContext();
//矩阵旋转45度(参数为图形上下文、旋转角度)是以左上角为旋转点的
//设置矩阵操作要在绘制前完成
// CGContextRotateCTM(ctx, M_PI_4);
//缩放(参数为图形上下文,x方向缩放比例,y方向缩放比例)
// CGContextScaleCTM(ctx, 0.5, 0.5);
//平移(参数为图形上下文,x方向平移距离,y方向平移距离)
CGContextTranslateCTM(ctx, 100, 100);
CGContextAddRect(ctx, CGRectMake(100, 100, 100, 100));
//标记
NSString *loc1 = @"1";
NSString *loc2 = @"2";
[loc1 drawAtPoint:CGPointMake(99, 99) withAttributes:nil];
[loc2 drawAtPoint:CGPointMake(201, 99) withAttributes:nil];
CGContextStrokePath(ctx);
剪切操作
一、剪切介绍
指剪切掉指定区域意外的部分,只保留该区域内的内容。
原则:先设置好剪切区域,或者说剪切方法,再去绘制相关内容。
二、代码相关
CGContextRef ctx = UIGraphicsGetCurrentContext();
//剪切自定义区域意外的部分(以剪切方法为三角形,剪切内容为图片为例)
// // CGContextAddEllipseInRect(ctx, CGRectMake(100, 100, 50, 50));
// CGContextMoveToPoint(ctx, 100, 100);
// CGContextAddLineToPoint(ctx, 60, 150);
// CGContextAddLineToPoint(ctx, 140, 150);
// CGContextClosePath(ctx);
// CGContextClip(ctx);
//剪切指定矩形区域意外的部分
CGContextClipToRect(ctx, CGRectMake(80, 100, 10, 10));
UIImage *image = [UIImage imageNamed:@"google"];
[image drawAtPoint:CGPointMake(80, 100)];
绘图路径
一、绘图路径介绍
我们之前画一条直线,都是直接设置好它的起点和终点,然后就开始画了。画一个圆,设置好圆心半径起点终点和方向即可。事实上,我们设置好这些绘图信息后,系统会默认创建一条绘图路径,画图就是根据这条路径来画的。一条线对应一条路径,一个圆对应另一条路径。那么我们自然可以通过手动创建路径的方式绘制,需要绘制几个图案,就要创建几条路径。
二、注意点
A、Quartz2D中所有通过creat/copy/retain方法创建出来的值都要释放,以path为例:
- CGPathRelease(path);或
- CFRelease(path);
B、可以将要绘制的所有路径加入到图形上下文后,最后一次性渲染。
三、代码示例
//绘制一条直线的两种方法(两种方式是等效的)
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextMoveToPoint(ctx, 20, 200);
CGContextAddLineToPoint(ctx, 300, 200);
CGContextStrokePath(ctx);
//手动创建路径绘制
//创建一条路径
CGMutablePathRef path = CGPathCreateMutable();
//添加绘图信息到路径
CGPathMoveToPoint(path, NULL, 20, 300);
CGPathAddLineToPoint(path, NULL, 300, 300);
//将路径添加到图形上下文
CGContextAddPath(ctx, path);
//创建另一条路径
CGMutablePathRef path2 = CGPathCreateMutable();
CGPathAddEllipseInRect(path2, NULL, CGRectMake(100, 400, 100, 100));
CGContextAddPath(ctx, path2);
CGContextStrokePath(ctx);
//渲染所有路径对应的图案
CGContextStrokePath(ctx);
//Quartz2D中所有通过creat/copy/retain方法创建出来的值都要释放
CGPathRelease(path);
CGPathRelease(path2);
//或者
// CFRelease(path);
运行结果
Bitmap
一、bitmap图形上下文
Quartz2D提供了以下几种类型的图形上下文
- Bitmap Graphics Context
- PDF Graphics Context
- Window Graphics Context
- Layer Graphics Context
- Printer Graphics Context
常用的是Bitmap Graphics Context。所谓Bitmap,其实就是UIImage,这也是最常用到的图形上下文,通常用它来生成一张图片。步骤如下:
- 创建一个bitmap图形上下文(有两种方式)
A.UIGraphicsBeginImageContext(<#CGSize size#>);
B.UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale);
这两种方法都可以创建一个bitmap图形上下文,但是第一种创建的图片清晰度和质量没有第二种好。方法二接收的参数依次为:
- size:指定创建出来的bitmap尺寸大小
- opaque:是否透明,YES表示不透明,NO透明
- scale:缩放比例,0为不缩放
- 获取创建好的bitmap图形上下文然后在上面做文章(画图)
- 取出绘制好的图片
- 关闭bitmap图形上下文
- 释放需要释放的变量
代码示例(截屏)
//点击按钮截屏
- (void)actionShot:(UIButton *)sender{
//可以隐藏按钮,渲染完后显示回来
self.buttonShot.hidden = YES;
//创建图形上下文
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.view.frame.size.width, self.view.frame.size.height), NO, 0);
//获取图形上下文并将当前屏幕渲染到图形上下文上
AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[delegate.window .layer renderInContext:UIGraphicsGetCurrentContext()];
//从图形上下文中取出绘制好的图片
UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭图形上下文
UIGraphicsEndImageContext();
self.buttonShot.hidden = NO;
// //截屏完毕 有时候可能想获取屏幕中指定区域的图片,如下操作
// //得到截屏的cgimage
CGImageRef image = screenImage.CGImage;
//设置目标区域,注意这里需要考虑retina分辨率的放大倍数,以iphone6plus为例,在原尺寸的基础上*3,这里就不判断了。
CGRect rect = CGRectMake(0, 0, screenImage.size.width*3, screenImage.size.height*3);
//取出目标区域的图片
CGImageRef targetImage = CGImageCreateWithImageInRect(image, rect);
//最终图片
UIImage *finalImage = [UIImage imageWithCGImage:targetImage];
//保存到相册
UIImageWriteToSavedPhotosAlbum(finalImage, self, @selector(image: didFinishSavingWithError:contextInfo:), nil);
//保存到沙盒
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString *currentTime = [dateFormatter stringFromDate:[NSDate date]];
NSString *imagePath = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"ScreenShot_%@.png",currentTime]];
NSData *imageDate = UIImagePNGRepresentation(finalImage);
[imageDate writeToFile:imagePath atomically:YES];
CGImageRelease(targetImage);
}
//保存至相册后的回调
- (void)image: (UIImage *) image didFinishSavingWithError: (NSError *) error contextInfo: (void *) contextInfo
{
NSString *msg = nil ;
if(error != NULL){
msg = @"保存图片失败" ;
}else{
msg = @"保存图片成功" ;
}
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"保存图片结果提示"
message:msg
delegate:self
cancelButtonTitle:@"确定"
otherButtonTitles:nil];
[alert show];
}
Quartz2D内存管理
前面讲绘图路径的时候提到过内存管理,下面再总结一下:
- 凡是使用含有“create”或“copy”的函数创建的对象,使用完毕后必需释放,否则将导致内存泄露。不含“create”或“copy”的则不需要释放。
- 如果retain了一个对象,不再使用时需要release。已CGColorSpace为例,如果创建了一个CGColorSpace对象,则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。也可以使用Core Foundation的CFRetain和CFRelease。注意不能传递NULL值给这些函数。
版权声明:本文为博主原创文章,未经博主允许不得转载。