Quartz2D
简介
Quartz2D是二维(平面)的绘图引擎(经包装的函数库,方便开发者使用。也就是说苹果帮我们封装了一套绘图的函数库)
同时支持iOS和Mac系统开发,用Quartz2D写的同一份代码,既可以运行在iphone上又可以运行在mac上,可以跨平台开发。
Quartz 2D能完成的工作
绘制图形 : 线条\三角形\矩形\圆\弧等
绘制文字
绘制\生成图片(图像)
读取\生成PDF
截图\裁剪图片
自定义UI控件
.....
Quartz2D在iOS开发中的价值
为了便于搭建美观的UI界面,iOS提供了UIKit框架,里面有各种各样的UI控件 UILabel:显示文字 UIImageView:显示图片 UIButton:同时显示图片和文字(能点击) … …
利用UIKit框架提供的控件,拼拼凑凑,能搭建和现实一些简单、常见的UI界面,但是,有些UI界面极其复杂、而且比较个性化,用普通的UI控件无法实现,这时可以利用Quartz2D技术将控件内部的结构画出来,自定义控件的样子,iOS中大部分控件的内容都是通过Quartz2D画出来的,因此,Quartz2D在iOS开发中很重要的一个价值是:自定义view(自定义UI控件)
自定义View
图形上下文(Graphics Context):是一个CGContextRef类型的数据
图形上下文的作用
保存绘图信息、绘图状态 -决定绘制的输出目标(绘制到什么地方去?) (输出目标可以是PDF文件、Bitmap或者显示器的窗口上) 相同的一套绘图序列,指定不同的Graphics Context,就可将相同的图像绘制到不同的目标上
自定义view的步骤
新建一个类,继承自UIView
实现- (void)drawRect:(CGRect)rect方法,然后在这个方法中
取得跟当前view相关联的图形上下文
绘制相应的图形内容
利用图形上下文将绘制的所有内容渲染显示到view上面
drawRect:方法
实现drawRect:方法才能绘图到view,因为在drawRect:方法中才能取得跟view相关联的图形上下文,
drawRect:方法在调用时间 当view第一次显示到屏幕上时(被加到UIWindow上显示出来) 调用view的setNeedsDisplay或者setNeedsDisplayInRect:时
在drawRect:方法中取得上下文后,就可以绘制东西到view上
View内部有个layer(图层)属性,drawRect:方法中取得的是一个Layer Graphics Context,因此,绘制的东西其实是绘制到view的layer上去了,View之所以能显示东西,完全是因为它内部的layer
示例1画直线
1> 获取图形上下文 CG:表示这个类在CoreGraphics框架里 Ref:引用 目前学的上下文都跟UIGraphics有关,想获取图形上下文,首先敲UIGraphics。
2> 拼接路径:一般开发中用贝塞尔路径,里面封装了很多东西,可以帮我画一些基本的线段,矩形,圆等等。 创建贝塞尔路径 起点:moveToPoint 终点:addLineToPoint
3> 把路径添加到上下文 CGPath转换:UIKit框架转CoreGraphics直接CGPath就能转
4> 把上下文渲染到视图,图形上下文本身不具备显示功能。 PPT画图分析为什么要这样做?首先获取图形上下文,然后描述路径,把路径添加到上下文,渲染到视图,图形上下文相当于一个内存缓存区,在内存里面操作是最快的,比直接在界面操作快多了。
在添加一根线 直接addLineToPoint,因为路径是拼接的,默认下一条线的起点是上一条线的终点。
画两跟不连接的线 1> 第二次画的时候,重新设置起点,然后画线。一个路径可以包含多条线段。 2> 新创建一个路径,添加到上下文。开发中建议使用这种,比较容易控制每根线。
设置绘图状态 线段怎么加粗。 绘图状态调用顺序:只要在渲染之前就好了,在渲染的时候才会去看绘图的最终状态。
// 1.获取跟当前view想关联的上下文// 以后只要根上下文有关,直接敲UIGraphics,一般都是以UIGraphics开头// CG:CoreGraphics Ref:引用CGContextRefctx =UIGraphicsGetCurrentContext();// 2.绘制内容,拼接路径,绘制的内容统称为路径// 在开发中一般使用贝塞尔路径,UIKit框架UIBezierPath*path = [UIBezierPathbezierPath];// 设置起点,移动到某个位置[path moveToPoint:CGPointMake(50,50)];// 添加一根线到某个点[path addLineToPoint:CGPointMake(200,200)];// 一根路径对象可以包含很多线段[path moveToPoint:CGPointMake(50,200)];// 默认下一根线的起点在上一根线的终点[path addLineToPoint:CGPointMake(100,200)];// 3.把路径添加到上下文,给上下文添加路径以CGContextCGContextAddPath(ctx, path.CGPath);// 开发中,如果线段不连接,最好使用一根线对应一个路径对象// 描述第二根线// path = [UIBezierPath bezierPath];//// // 设置起点,移动到某个位置// [path moveToPoint:CGPointMake(200, 200)];//// // 添加一根线到某个点// [path addLineToPoint:CGPointMake(100, 200)];//// CGContextAddPath(ctx, path.CGPath);// 4.把上下文内容渲染到viewCGContextStrokePath(ctx);
示例2画圆形
//Center:圆心//radius:半径.//startAngle:开始角度//endAngle:结束角度//clockwise:CGPointcenter =CGPointMake(self.bounds.size.width*0.5,self.bounds.size.height*0.5);CGFloatradius =100;CGFloatstartA =0;//圆的0度角在圆的最右侧.CGFloatendA = -M_PI_2;UIBezierPath*path = [UIBezierPathbezierPathWithArcCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES]; [UIColorredColor] set]; [path stroke];
示例3画文字,常见属性
//我们要画的文字NSString*str =@"我们要画的文字";//开始绘制文字// AtPoint:画在哪个点上.//Attributes:文字的属性.颜色,字体大小...NSMutableDictionary*dict = [NSMutableDictionarydictionary];//设置文字的颜色dict[NSForegroundColorAttributeName] = [UIColorredColor];//设置字体大小dict[NSFontAttributeName] = [UIFontsystemFontOfSize:50];//设置描边的颜色dict[NSStrokeColorAttributeName] = [UIColorblueColor];//设置描边的宽度dict[NSStrokeWidthAttributeName] = @1;NSShadow*shadow = [[NSShadowalloc] init];//设置阴影的偏移量shadow.shadowOffset=CGSizeMake(-10,10);//设置阴影的颜色shadow.shadowColor= [UIColorgreenColor];//设置阴影的模糊shadow.shadowBlurRadius=2; dict[NSShadowAttributeName] = shadow;//drawAtPoint不会自动换行// [str drawAtPoint:CGPointZero withAttributes:dict];//drawInRect它会自动的换行.[str drawInRect:self.boundswithAttributes:dict];
图形上下文的矩阵操作
形变操作,必须得要在添加路径之前进行
计时器
CADisplayLink
//在绘图当中, 我们一般使用CADisplayLink.因为他和setNeedsDisplay调用时机是一样的,都是当下一次屏幕刷新的时候调用.
//CADisplayLink//它是当每次屏幕刷新的时候就会调用定时器方法.(每一秒种刷新60次)CADisplayLink*link = [CADisplayLinkdisplayLinkWithTarget:selfselector:@selector(update)];//想让定时器工作,必须得要把它添加到主运行循环.[link addToRunLoop:[NSRunLoopmainRunLoop] forMode:NSDefaultRunLoopMode];
NSTimer
[NSTimerscheduledTimerWithTimeInterval:0.01target:selfselector:@selector(update) userInfo:nilrepeats:YES];
注意,在实际开发中视情况选择使用
常用的拼接函数
新建一个起点voidCGContextMoveToPoint(CGContextRefc,CGFloatx,CGFloaty)添加新的线段到某个点voidCGContextAddLineToPoint(CGContextRefc,CGFloatx,CGFloaty)添加一个矩形voidCGContextAddRect(CGContextRefc,CGRectrect)添加一个椭圆voidCGContextAddEllipseInRect(CGContextRefcontext,CGRectrect)添加一个圆弧voidCGContextAddArc(CGContextRefc,CGFloatx,CGFloaty,CGFloatradius,CGFloatstartAngle,CGFloatendAngle,intclockwise)
常用绘制的函数
Mode参数决定绘制的模式
voidCGContextDrawPath(CGContextRefc,CGPathDrawingModemode)绘制空心路径voidCGContextStrokePath(CGContextRefc)绘制实心路径voidCGContextFillPath(CGContextRefc)提示:一般以CGContextDraw、CGContextStroke、CGContextFill开头的函数,都是用来绘制路径的
矩阵操作
利用矩阵操作,能让绘制到上下文中的所有路径一起发生变化缩放voidCGContextScaleCTM(CGContextRefc,CGFloatsx,CGFloatsy)旋转voidCGContextRotateCTM(CGContextRefc,CGFloatangle)平移voidCGContextTranslateCTM(CGContextRefc,CGFloattx,CGFloatty)
图片水印技术
需求:在手机客户端app中需要用到水印技术,用户拍完照片后,可以在照片上打个水印,标识这个图片是属于哪个用户的
实现方式:利用Quartz2D,将水印(文字、LOGO)画到图片的右下角
核心代码
开启一个基于位图的图形上下文UIGraphicsBeginImageContextWithOptions(CGSizesize,BOOLopaque,CGFloatscale)从上下文中取得图片(UIImage)UIImage*UIGraphicsGetImageFromCurrentImageContext();结束基于位图的图形上下文UIGraphicsEndImageContext();
水印PPT简介
图片水印作用:防止他人盗取图片,加一些Logo,生成一张新的图片。
和绘图一样的生成新的图片,需要拿到上下文做事情,这里也需要拿到上下文,生成一个新的图片。
位图上下文,在这个上下文画东西,就能输出到新的图片上。
之前用的都是图层上下文,系统会自动创建,但是我们位图上下文,需要我们手动创建
总结:只要不和view有关系的上下文,都需要我们手动创建。
在哪获取图像上下文,viewDidLoad, 不需要拿到系统创建的图层上下文,没必要在drawRect方法里写,直接viewDidLoad就行了。
UIGraphicsBeginImageContextWithOptions:(CGSize size, BOOL opaque, CGFloat scale)创建一个位图上下文,而且这种方法得到的图片最清晰。解释参数(size:新图片尺寸 opaque: YES:不透明 NO:透明 scale:0.0 不伸缩)
绘制内容(图片,文字)
获取图片:把位图上下文的内容生成一个图片给你。
关闭上下文,不关闭一直占用着内存。
显示UIImageView上
保存图片,写到文件,UIImage不能写,需要转换成NSData二进制数据
UIImageJPEGRepresentation:可以设置图片质量
UIImagePNGRepresentation:把图片转换成png格式的二进制数据,png格式默认是最高清的。
写到桌面
图片裁剪
图片裁剪
PPT分析思路:先设置裁剪区域,把图片画上去,超出裁剪区域的自动裁剪掉。
加载旧图片,根据旧图片,获取上下文尺寸。
上下文的尺寸 = 新图片的尺寸
开启一个多大的上下文?:和图片尺寸一样大,避免压缩图片。如果比图片尺寸小,会压缩图片。
设置裁剪区域:正切于图片的圆
绘制旧图片
获取新图片
关闭上下文
//1.加载要裁剪的图片UIImage*image = [UIImageimageNamed:@"阿狸头像"];
//2.开启一个跟图片尺寸一样大的位图上下文UIGraphicsBeginImageContextWithOptions(image.size,NO,0);
//3.设置一个裁剪(宽度和高度都是等于原始图片的宽高)UIBezierPath*path = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0, image.size.width, image.size.height)];//把路径设置成裁剪区域[path addClip];
//4.把要裁剪的图片绘制到上下文当中.[image drawAtPoint:CGPointZero];
//5.从上下文当中生成一张新的图片UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
//6.关闭上下文.UIGraphicsEndImageContext();self.imageV.image= newImage;
带圆环裁剪:在裁剪的图片外边加个小圆环。
先画一个大圆,在设置裁剪区域,把图片画上去,超出裁剪区域的自动裁剪掉。
加载旧图片,根据旧图片,获取上下文尺寸。
确定圆环宽度 borderW
上下文的尺寸 = 新图片的尺寸
确定新的上下文尺寸: newImageW : oldImageW + 2borderW newImageH : oldImageH + 2borderW,
绘制大圆: 1.获取上下文 2.添加路径到上下文 3.设置大圆的颜色 = 圆环的颜色 4.渲染
设置裁剪区域,和图片尺寸一样大,只不过,x,y不一样,x=borderW,y=borderW.
绘制旧图片
获取新图片
关闭上下文
抽分类,3个参数,图片名称,圆环宽度,圆环颜色
可以创建给UIImage添加一个分类提供方法直接使用
//0.设置一个边框宽度//1.加载图片//2.开启一个位图上下文CGSizesize =CGSizeMake(image.size.width+2* boderW, image.size.height+2* boderW);UIGraphicsBeginImageContextWithOptions(size,NO,0);
//3.设置一个大圆的一个填充区域UIBezierPath*path = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(0,0, size.width, size.height)];//设置边框的颜色[color set]; [path fill];
//4.设置一个小圆裁剪区域path = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(boderW, boderW, image.size.width, image.size.height)];//把小圆设置成裁剪区域[path addClip];
//5.把原始图片绘制到上下文当中[image drawInRect:CGRectMake(boderW, boderW, image.size.width, image.size.height)];
//6.生成一张新的图片UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
//7.关闭上下文.UIGraphicsEndImageContext();returnnewImage;
图片截屏
屏幕截图:把屏幕的内容截屏生成一张新的图片
通常开发中,都是把控制器的内容截屏,生成新的图片
控制器根据view显示
view根据layer图层显示
把layer渲染到位图上下文
注意:图层只能用渲染,图片和文字可以用draw
渲染在新的图片
开启图片上下文,和视图一样的尺寸
写入桌面
抽分
//懒加载,当使用才创建,可以节省内存-(UIView*)cover{if(_cover ==nil) {//创建一个遮盖UIView*cover = [[UIViewalloc] init]; cover.backgroundColor= [UIColorblackColor]; cover.alpha=0.7; _cover = cover; [self.viewaddSubview:cover]; }return_cover;}
- (void)viewDidLoad { [superviewDidLoad];// Do any additional setup after loading the view, typically from a nib.}
//当手指拖动的时候调用- (IBAction)pan:(UIPanGestureRecognizer*)pan {//获取当前手指所在的点CGPointcurP = [pan locationInView:self.imageV];//起点if(pan.state==UIGestureRecognizerStateBegan){//添加遮盖,设置遮盖的起点.self.startP= curP; }elseif(pan.state==UIGestureRecognizerStateChanged){//移动,确定遮盖的尺寸//x轴偏移量CGFloatoffsetX = curP.x-self.startP.x;//Y 轴的偏移量CGFloatoffsetY = curP.y-self.startP.y;//改变遮盖的尺寸self.cover.frame=CGRectMake(self.startP.x,self.startP.y, offsetX, offsetY); }else if(pan.state==UIGestureRecognizerStateEnded){//松开//做裁剪
//开启一个上下文.(跟图片一样大的上下文.)UIGraphicsBeginImageContextWithOptions(self.imageV.bounds.size,NO,0);
//在上下文当中设置一个裁剪区域(就是当前遮盖的frame)UIBezierPath*path = [UIBezierPathbezierPathWithRect:self.cover.frame];
//设置裁剪区域[path addClip];//把UIImageView当中的图片绘制到上下文当中.
//获取当前上下文CGContextRefctx =UIGraphicsGetCurrentContext(); [self.imageV.layerrenderInContext:ctx];
//生成一张新的图片.UIImage*newImage =UIGraphicsGetImageFromCurrentImageContext();
//把位图上下文关闭掉UIGraphicsEndImageContext();
//把UIImageView当中的图片重新赋值self.imageV.image= newImage;//移除遮盖[self.coverremoveFromSuperview]; }}
手势解锁
分析界面有几个控件:背景:UIImageView 白色圆圈:按钮(点击他,会出现另外一种图片,按钮可以设置不同状态下的图片。)单独视图:(画线是有范围的,当超出view就不能画线了)
HMLoadView:自定义视图,在视图一创建的时候,就添加9个按钮。
在initWithCoder,initWithFrame方法添加按钮。
九宫格布局:
tolcol =3计算row,col 按钮的x,y跟col,row有关系,col = i % tolcol row = i / tolcol 计算边距 margin = (view.bounds.size.width- tolcol * btnW) / (tolcol +1) btnX = margin + (btnW + margin) * col btnY = (btnW + margin) * row
圆的选中
点击按钮就为选中的图片怎么做?监听按钮点击。
不能addTarget: 不能及时显示选中图片。
监听touchBegin,判断点在不在按钮的frame上。
touchBegin不调用?原因:事件交给按钮处理,应该把事件交给解锁视图。让按钮不接收事件。
设置按钮不允许交互,2个用处:1.不接收事件 2.取消高亮效果 一举两得
遍历所有按钮,看触摸点在哪个按钮上,就选中谁,CGRectContainsPoint 传入的参数必须是同一个坐标系
实现touchMove方法:因为手指移动的时候,也需要判断点在不在按钮上。
抽方法,因为touchMove的方法里,也需要做同样的事情。
1>pointWithTouches根据touches集合取出触摸点2>buttonWithPoint根据触摸点,获取触摸按钮
圆的连线
被选中按钮之间都需要连线,还有一个多余的线
先把选中的按钮全部连线,因为多余的那根线是从最后一个按钮的圆心开始画,手指移动在哪就画哪。
搞个数组保存下所有选中按钮,在drawRect方法中遍历所有按钮,连线
UIBezierPath画线,不需要上下文。
需要多少个UIBezierPath对象?一个,路径都是连续的,不相连的,才需要创建新的UIBezierPath。
遍历数组,描述路径 1> 起点:第一个按钮的圆心 2> 添加一根线到其他按钮的圆心
设置路径的颜色和线宽
把所有路径都描述完就,渲染一次就够了。
[path stroke] 就能渲染到视图上了。
画多余的那根线,记住手指移动的位置。
setNeedDisplay 因为drawRect只会调用一次,需要每次手指移动的时候,都需要重绘。
touchBegin不调用setNeedDisplay,因为重绘也没用,只有起点。
lineJoinStyle:有尖尖的东西,线段连接样式的问题,设置为平的。
已经选中的按钮,不需要再次选中,和画线
手指抬起,取消所有选中按钮,并且清空数组,清空线条
drawRect判断下没有选中数组,不需要画线。
如何判断用户是否输入正确?给选中按钮绑定tag,遍历所有选中按钮,把tag拼接成一个字符串。
画板
分析控件:ToolBar(不需要管里面子控件的frame),画板view,自定义工具条(方便屏幕适配,能迅速固定里面子控件的位置)
自动布局,四个约束确定一个控件
蓝色按钮:左,右,下,高固定
橘色按钮:右,下固定,宽度和高度和蓝色按钮相等。
绿色按钮:右,下,宽度和高度和橘色按钮相等
绘图思路:先描述路径,在渲染,需要画很多线条,最好每个线条保存到一个路径里面。
绘图功能
touchBegin设置画线起点:开始触摸的点
创建UIBezierPath,贝塞尔路径才能设置起点
touchMove:手指移动到哪就画哪,addLine到移动的点
setNeedDisplay,路径描述完了,就渲染到视图就好了。
drawRect方法每次都会把之前的清掉,重新绘制
搞个数组保存上一次的,绘制多条线
设置线宽:每次滑动滑块,就改变下一次路径的宽度
监听滑块的值,把值传递给paintView,设置路径的线宽。
不能在drawRect写,会导致所有路径都是一个线宽,应该是每个路径都记录自己的线宽,而且线宽只需要设置一次,在路径一创建的时候就设置。
设置颜色:
自定义UIBezierPath,保存颜色,实现一条路径对应一个颜色 辅助功能:
清屏:清空所有路径数组
撤销:移除最后一条路径
橡皮擦:设置画笔为白色
保存:
1> 把画板内容截屏
2> 把图片保存到相册 UIImageWriteToSavedPhotosAlbum
3> 保存相册的回调方法不能乱写,必须按照规定 image:didFinishSavingWithError:contextInfo:
照片选择
1> 通常都是去相册里去照片
2> UIImagePickerController,就可以去手机相册了
3> 用Modal,没有导航控制器,不能push
4> 设置代理,获取图片
5> 把图片传递给paintView
6> 添加到路径,然后重绘。:画图片也需要顺序的
照片处理
1>搞一个和画板一样的透明view,里面搞个UIImageView来显示我们从照片库选择的图片,然后对UIImageView进行旋转,缩放等操作
2> UImageView不能放在layoutSubViews里面设置尺寸,因为要设置他的形变,默认会调用他父类的layoutSubViews,导致一些莫名其妙的原因
3> 在传图片的时候设置他的尺寸,和位置,让他和图片一样的尺寸,显示在中间
4> 长按操作:在长按结束的时候,做操作
1.默认高亮状态,先变浅在恢复,设置alpha
2.动画结束后,把自己截屏,传给控制器里,在交给paintView显示
3.需要移除父视图,使命完成了,而且不移除,不能绘制东西,永远添加到paintView上面