IOS绘图探索

来源:互联网 发布:springmvc 加载js 编辑:程序博客网 时间:2024/05/17 03:08

参考:http://www.cocoachina.com/industry/20140115/7703.html

参考:http://blog.sina.com.cn/s/blog_6b60259a0101c90g.html

参考原文:http://www.cnblogs.com/xdream86/archive/2012/12/12/2814552.html

UIBazier使用参考:http://blog.csdn.net/guo_hongjun1611/article/details/7839371

CGPathRef使用参考:http://blog.sina.com.cn/s/blog_7ff0f30f01011hkl.html

CAShapLayer参考http://blog.csdn.net/volcan1987/article/details/9969455

CAGradientLayer 使用参考http://www.cocoachina.com/industry/20140705/9039.html
简单绘图入门:转载:http://code.cocoachina.com/view/131670

Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎。它提供了低级别、轻量级、高保真度的2D渲染该框架可以用于基于路径的绘图、变换、颜色管理、脱屏渲染,模板、渐变、遮蔽、图像数据管理、图像的创建、遮罩以及PDF文档的创建、显示和分析。为了从感官上对这些概念做一个入门的认识,你可以运行一下官方的example code
 
iOS支持两套图形API族:Core Graphics/QuartZ 2D 和OpenGL ES
OpenGL ES是跨平台的图形API,属于OpenGL的一个简化版本。
QuartZ 2D是苹果公司开发的一套API,它是Core Graphics Framework的一部分。需要注意的是:OpenGL ES是应用程序编程接口,该接口描述了方法、结构、函数应具有的行为以及应该如何被使用的语义。也就是说它只定义了一套规范,具体的实现由设备制造商根据规范去做。而往往很多人对接口和实现存在误解。举一个不恰当的比喻:上发条的时钟和装电池的时钟都有相同的可视行为,但两者的内部实现截然不同。因为制造商可以自由的实现Open GL ES,所以不同系统实现的OpenGL ES也存在着巨大的性能差异。
 
Core Graphics API所有的操作都在一个上下文中进行。所以在绘图之前需要获取该上下文并传入执行渲染的函数中。如果你正在渲染一副在内存中的图片,此时就需要传入图片所属的上下文。获得一个图形上下文是我们完成绘图任务的第一步,你可以将图形上下文理解为一块画布。如果你没有得到这块画布,那么你就无法完成任何绘图操作。当然,有许多方式获得一个图形上下文,这里我介绍两种最为常用的获取方法。
 
第一种方法就是创建一个图片类型的上下文。调用UIGraphicsBeginImageContextWithOptions函数就可获得用来处理图片的图形上下文。利用该上下文,你就可以在其上进行绘图,并生成图片。调用UIGraphicsGetImageFromCurrentImageContext函数可从当前上下文中获取一个UIImage对象。记住在你所有的绘图操作后别忘了调用UIGraphicsEndImageContext函数关闭图形上下文。
 
第二种方法是利用cocoa为你生成的图形上下文。当你子类化了一个UIView并实现了自己的drawRect:方法后,一旦drawRect:方法被调用,Cocoa就会为你创建一个图形上下文,此时你对图形上下文的所有绘图操作都会显示在UIView上。
 
判断一个上下文是否为当前图形上下文需要注意的几点:
1.UIGraphicsBeginImageContextWithOptions函数不仅仅是创建了一个适用于图形操作的上下文,并且该上下文也属于当前上下文。
2.当drawRect方法被调用时,UIView的绘图上下文属于当前图形上下文。
3.回调方法所持有的context:参数并不会让任何上下文成为当前图形上下文。此参数仅仅是对一个图形上下文的引用罢了。
 
作为初学者,很容易被UIKit和Core Graphics两个支持绘图的框架迷惑。
 
UIKit
像UIImage、NSString(绘制文本)、UIBezierPath(绘制形状)、UIColor都知道如何绘制自己。这些类提供了功能有限但使用方便的方法来让我们完成绘图任务。一般情况下,UIKit就是我们所需要的。
使用UiKit,你只能在当前上下文中绘图,所以如果你当前处于UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,你就可以直接使用UIKit提供的方法进行绘图。如果你持有一个context:参数,那么使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。幸运的是,调用UIGraphicsPushContext 函数可以方便的将context:参数转化为当前上下文,记住最后别忘了调用UIGraphicsPopContext函数恢复上下文环境。
 
Core Graphics
这是一个绘图专用的API族,它经常被称为QuartZ或QuartZ 2D。Core Graphics是iOS上所有绘图功能的基石,包括UIKit。
使用Core Graphics之前需要指定一个用于绘图的图形上下文(CGContextRef),这个图形上下文会在每个绘图函数中都会被用到。如果你持有一个图形上下文context:参数,那么你等同于有了一个图形上下文,这个上下文也许就是你需要用来绘图的那个。如果你当前处于UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,并没有引用一个上下文。为了使用Core Graphics,你可以调用UIGraphicsGetCurrentContext函数获得当前的图形上下文。
 
至此,我们有了两大绘图框架的支持以及三种获得图形上下文的方法(drawRect:、drawRect: inContext:、UIGraphicsBeginImageContextWithOptions)。那么我们就有6种绘图的形式。如果你有些困惑了,不用怕,我接下来将说明这6种情况。无需担心还没有具体的绘图命令,你只需关注上下文如何被创建以及我们是在使用UIKit还是Core Graphics。

绘制边框:
- (void)drawRect:(CGRect)rect{    CGContextRef context = UIGraphicsGetCurrentContext();    CGMutablePathRef path = CGPathCreateMutable();        CGPathAddRect(path, nil, rect);    CGContextAddPath(context, path);    //[[UIColor colorWithWhite:1.0f alpha:0.0f]setFill];    [[UIColor colorWithRed:0.5 green:0.1 blue:0.3 alpha:1] setStroke];    CGContextSetLineWidth(context, 20.f);    CGContextDrawPath(context, kCGPathStroke);    CGPathRelease(path);}

运行结果:

绘制圆:
- (void)drawRect:(CGRect)rect {    CGContextRef content = UIGraphicsGetCurrentContext() ;    //线的颜色    UIColor* colorLine = [UIColor redColor] ;    [colorLine setStroke];    CGContextAddArc(content, rect.size.width/2, rect.size.height/2, 10, 0, 2*3.1415926, 0) ;    CGContextStrokePath(content) ;}

运行结果:

CAShapeLayer和UIBazierPath绘制圆
#import "KACircleProgressView.h"@interface KACircleProgressView : UIView {    CAShapeLayer *_trackLayer;    UIBezierPath *_trackPath;    CAShapeLayer *_progressLayer;    UIBezierPath *_progressPath;}@property (nonatomic, strong) UIColor *trackColor;@property (nonatomic, strong) UIColor *progressColor;@property (nonatomic) float progress;//0~1之间的数@property (nonatomic) float progressWidth;- (void)setProgress:(float)progress animated:(BOOL)animated;@end@implementation KACircleProgressView- (id)initWithFrame:(CGRect)frame{    self = [super initWithFrame:frame];    if (self) {        // Initialization code        _trackLayer = [CAShapeLayer new];        [self.layer addSublayer:_trackLayer];        _trackLayer.fillColor = nil;        _trackLayer.frame = self.bounds;                _progressLayer = [CAShapeLayer new];        [self.layer addSublayer:_progressLayer];        _progressLayer.fillColor = nil;        _progressLayer.lineCap = kCALineCapRound;        _progressLayer.frame = self.bounds;                //默认5        self.progressWidth = 5;    }    return self;}- (void)setTrack{    _trackPath = [UIBezierPath bezierPathWithArcCenter:self.center radius:(self.bounds.size.width - _progressWidth)/ 2 startAngle:0 endAngle:M_PI * 2 clockwise:YES];;    _trackLayer.path = _trackPath.CGPath;}- (void)setProgress{    _progressPath = [UIBezierPath bezierPathWithArcCenter:self.center radius:(self.bounds.size.width - _progressWidth)/ 2 startAngle:- M_PI_2 endAngle:(M_PI * 2) * _progress - M_PI_2 clockwise:YES];    _progressLayer.path = _progressPath.CGPath;}- (void)setProgressWidth:(float)progressWidth{    _progressWidth = progressWidth;    _trackLayer.lineWidth = _progressWidth;    _progressLayer.lineWidth = _progressWidth;        [self setTrack];    [self setProgress];}- (void)setTrackColor:(UIColor *)trackColor{    _trackLayer.strokeColor = trackColor.CGColor;}- (void)setProgressColor:(UIColor *)progressColor{    _progressLayer.strokeColor = progressColor.CGColor;}- (void)setProgress:(float)progress{    _progress = progress;        [self setProgress];}- (void)setProgress:(float)progress animated:(BOOL)animated{    }

调用方式:
@implementation ViewController- (void)viewDidLoad {    KACircleProgressView *progress = [[KACircleProgressView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];    [self.view addSubview:progress];    progress.trackColor = [UIColor blackColor];    progress.progressColor = [UIColor orangeColor];    progress.progress = .7;    progress.progressWidth = 20;}

运行结果:


以下在View 的drawRect方法中使用UIKit绘制矩形 和使用Core Graphics 在矩形中绘制圆

- (void)drawRect:(CGRect)rect {    //绘制矩形    UIBezierPath *bezierpath=[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 100)];    [[UIColor yellowColor] setFill];    [bezierpath fill];        //在矩形中绘制圆形    CGContextRef con=UIGraphicsGetCurrentContext();    CGContextAddEllipseInRect(con, CGRectMake(0, 0, 100, 100));    CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);    CGContextFillPath(con);    }

运行结果

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)cox 调用,同时写了drawRect会将 drawRect的 UIGraphics操作给覆盖掉

////这个方法实现会把drawRect 给屏蔽掉-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{    UIGraphicsPushContext(ctx);        UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,80,80)];        [[UIColor groupTableViewBackgroundColor] setFill];        [p fill];    }

以上两个方法调用方式:
    cutomView*view=[[cutomView alloc]initWithFrame:CGRectMake(0, 0, 320, 200)];    view.backgroundColor=[UIColor cyanColor];    [self.view addSubview:view];        cutomView* v=[[cutomView alloc]initWithFrame:CGRectMake(0, 220, 320, 200)];    CALayer*l=[CALayer layer];    l.cornerRadius=10;    l.delegate=self;    [v.layer addSublayer:l];    [l setNeedsDisplay];    [self.view addSubview:v];

以下在View 的drawRect方法中使用Core Graphics 在矩形中擦除掉一个小方块
- (void)drawRect:(CGRect)rect {    self.backgroundColor=[UIColor whiteColor];    CGContextRef con = UIGraphicsGetCurrentContext();        CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);        CGContextFillRect(con, rect);        CGContextClearRect(con, CGRectMake(0,0,30,30));}

运行结果


将一张图片转换成一个圆形图片.利用UIGraphicsEndImageContext

    UIImageView* imageView1=[[UIImageView alloc]initWithFrame:CGRectMake(10, 10, 200, 200)];    UIImage* img=[UIImage imageNamed:@"a.png"];    imageView1.image=img;    [self.view addSubview:imageView1];        UIImageView* imageView2=[[UIImageView alloc]initWithFrame:CGRectMake(10, 220, 200, 200)];    UIImage* img2=[UIImage imageNamed:@"a.png"];        UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);    UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];        [img2 drawAtPoint:CGPointMake(100, 0)];        [p fill];        UIImage* im = UIGraphicsGetImageFromCurrentImageContext();    UIGraphicsEndImageContext();    imageView2.image=im;    [self.view addSubview:imageView2];    
运行结果


利用UIGraphicsBeginImageContextWithOptions和UIGraphicsGetCurrentContext绘制一个圆形


    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO, 0);        CGContextRef con = UIGraphicsGetCurrentContext();        CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));        CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);        CGContextFillPath(con);        UIImage* im = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();    imageView1.image=im;    [self.view addSubview:imageView1];

 运行结果



利用UIGraphicsBeginImageContextWithOptions平移三张图片,三张图片成为一张

    //平移三张    UIImage* mars = [UIImage imageNamed:@"1.jpg"];        CGSize sz = [mars size];        UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*3, sz.height), NO, 0);        [mars drawAtPoint:CGPointMake(0,0)];        [mars drawAtPoint:CGPointMake(sz.width,0)];    [mars drawAtPoint:CGPointMake(sz.width*2, 0)];        UIImage* im3 = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();        UIImageView* iv = [[UIImageView alloc] initWithImage:im3];    iv.frame=CGRectMake(10, 20, 240, 100);    [self.view addSubview:iv];

运行结果



在一张大图片获得一张小图片

 UIImage* mars = [UIImage imageNamed:@"1.jpg"];        CGSize sz = [mars size];        UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*2, sz.height*2), NO, 0);        [mars drawInRect:CGRectMake(0,0,sz.width*2,sz.height*2)];        [mars drawInRect:CGRectMake(sz.width/2.0, sz.height/2.0, sz.width, sz.height) blendMode:kCGBlendModeMultiply alpha:1.0];        UIImage* im_suofang = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();        UIImageView* iv = [[UIImageView alloc] initWithImage:im_suofang];        iv.frame=CGRectMake(10, 20, 150, 200);    [self.view addSubview:iv];

运行结果



获得一张图片的右半边

 //获得图片的右半边      UIImage* mars = [UIImage imageNamed:@"10.jpg"];        CGSize sz = [mars size];        UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width/2.0, sz.height), NO, 0);        [mars drawAtPoint:CGPointMake(-sz.width/2.0, 0)];        UIImage* im_right = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();        UIImageView* iv = [[UIImageView alloc] initWithImage:im_right];        iv.frame=CGRectMake(10, 20, 150, 200);    [self.view addSubview:iv];

运行结果:




将图片分开拆成两张图片,分别绘制在上下文的左右两边

 //下面的代码展示了将图片拆分成两半,并分别绘制在上下文的左右两边:    UIImage* mars = [UIImage imageNamed:@"33.jpg"];    UIImageView* iv1 = [[UIImageView alloc] initWithImage:mars];        iv1.frame=CGRectMake(10, 20, 150, 200);    [self.view addSubview:iv1];        // 抽取图片的左右半边        CGSize sz = [mars size];        CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(0,0,sz.width/2.0,sz.height));        CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));        // 将每一个CGImage绘制到图形上下文中        UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);        CGContextRef con1 = UIGraphicsGetCurrentContext();        CGContextDrawImage(con1, CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);        CGContextDrawImage(con1, CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);        UIImage* im_both = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();        // 记得释放内存,ARC在这里无效         CGImageRelease(marsLeft);         CGImageRelease(marsRight);        UIImageView* iv = [[UIImageView alloc] initWithImage:im_both];        iv.frame=CGRectMake(10, 240, 150, 200);    [self.view addSubview:iv];

运行结果,不要YY哦



你也许发现绘出的图是上下颠倒的!图片的颠倒并不是因为被旋转了。当你创建了一个CGImage并使用CGContextDrawImage方法绘图就会引起这种问题。这主要是因为原始的本地坐标系统(坐标原点在左上角)与目标上下文(坐标原点在左下角)不匹配

以下为修正方案

    //下面的代码展示了将图片拆分成两半,并分别绘制在上下文的左右两边:    UIImage* mars = [UIImage imageNamed:@"33.jpg"];        // 抽取图片的左右半边        CGSize sz = [mars size];        CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(0,0,sz.width/2.0,sz.height));        CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));        // 将每一个CGImage绘制到图形上下文中        UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);        CGContextRef con1 = UIGraphicsGetCurrentContext();        CGContextDrawImage(con1, CGRectMake(0,0,sz.width/2.0,sz.height), flip(marsLeft));        CGContextDrawImage(con1, CGRectMake(sz.width,0,sz.width/2.0,sz.height), flip(marsRight));        UIImage* im1 = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();        // 记得释放内存,ARC在这里无效        CGImageRelease(marsLeft);        CGImageRelease(marsRight);        UIImageView* iv = [[UIImageView alloc] initWithImage:im1];        iv.frame=CGRectMake(10, 20, 150, 200);    [self.view addSubview:iv];

运行结果


    //在双分辨率的设备上,如果我们的图片文件是高分辨率(@2x)版本,上面的绘图就是错误的。原因在于对于UIImage来说,在加载原始图片时使用imageNamed:方法,它会自动根据所在设备的分辨率类型选择图片,并且UIImage通过设置用来适配的scale属性补偿图片的两倍尺寸。但是一个CGImage对象并没有scale属性,它不知道图片文件的尺寸是否为两倍!所以当调用UIImageCGImage方法,你不能假定所获得的CGImage尺寸与原始UIImage是一样的。在单分辨率和双分辨率下,一个UIImage对象的size属性值都是一样的,但是双分辨率UIImage对应的CGImage是单分辨率UIImage对应的CGImage的两倍大

    


另外一种处理倒置问题

    //这里还有另一种修复倒置问题的方案。相对于使用flip函数,你可以在绘图之前将CGImage包装进UIImage中,这样做有两大优点:    //1.当UIImage绘图时它会自动修复倒置问题    //2.当你从CGImage转化为Uimage时,可调用imageWithCGImage:scale:orientation:方法生成CGImage作为对缩放性的补偿。        UIImage* mars = [UIImage imageNamed:@"13.jpg"];        CGSize sz = [mars size];        CGImageRef marsCG = [mars CGImage];        CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG));        CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));        CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));        UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0);        [[UIImage imageWithCGImage:marsLeft scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(0,0)];        [[UIImage imageWithCGImage:marsRight scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(sz.width,0)];        UIImage* im1 = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();        CGImageRelease(marsLeft); CGImageRelease(marsRight);        UIImageView* iv = [[UIImageView alloc] initWithImage:im1];        iv.frame=CGRectMake(10, 20, 150, 200);    [self.view addSubview:iv];


CGImageRef flip (CGImageRef im) {        CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im));        UIGraphicsBeginImageContextWithOptions(sz, NO, 0);        CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, sz.width, sz.height), im);        CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];        UIGraphicsEndImageContext();        return result;     }



运行结果:



滤镜


    UIImage* moi = [UIImage imageNamed:@"Mars.jpeg"];        CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage];        CIFilter* grad = [CIFilter filterWithName:@"CIRadialGradient"];        CIVector* center = [CIVector vectorWithX:moi.size.width / 2.0 Y:moi.size.height / 2.0];        // 使用setValue:forKey:方法设置滤镜属性        [grad setValue:center forKey:@"inputCenter"];        // 在指定滤镜名时提供所有滤镜键值对        CIFilter* dark = [CIFilter filterWithName:@"CIDarkenBlendMode" keysAndValues:@"inputImage", grad.outputImage, @"inputBackgroundImage", moi2, nil];        CIContext* c = [CIContext contextWithOptions:nil];        CGImageRef moi3 = [c createCGImage:dark.outputImage fromRect:moi2.extent];        UIImage* moi4 = [UIImage imageWithCGImage:moi3 scale:moi.scale orientation:moi.imageOrientation];        CGImageRelease(moi3);        UIImageView* iv1 = [[UIImageView alloc] initWithImage:moi4];        iv1.frame=CGRectMake(10, 20, 150, 200);    [self.view addSubview:iv1];

运行结果


以下是绘制箭头的N种方法


#import "ArrowheadView.h"@implementation ArrowheadView// Only override drawRect: if you perform custom drawing.// An empty implementation adversely affects performance during animation.- (void)drawRect:(CGRect)rect {    CGContextRef con = UIGraphicsGetCurrentContext();#if 0    // 绘制一个黑色的垂直黑色线,作为箭头的杆子        CGContextMoveToPoint(con, 100, 100);        CGContextAddLineToPoint(con, 100, 19);        CGContextSetLineWidth(con, 20);        CGContextStrokePath(con);        // 绘制一个红色三角形箭头        CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);        CGContextMoveToPoint(con, 80, 25);        CGContextAddLineToPoint(con, 100, 0);        CGContextAddLineToPoint(con, 120, 25);        CGContextFillPath(con);    // 从箭头杆子上裁掉一个三角形,使用清除混合模式        CGContextSaveGState(con);        {          CGContextSetFillColorWithColor(con, [[UIColor whiteColor] CGColor]);        CGContextMoveToPoint(con, 90, 101);                CGContextAddLineToPoint(con, 100, 90);                CGContextAddLineToPoint(con, 110, 101);                CGContextSetBlendMode(con, kCGBlendModeClear);                CGContextFillPath(con);                    }        CGContextRestoreGState(con);    #endif    /**     *  如果一段路径需要重用或共享,你可以将路径封装为CGPath(具体类型是CGPathRef)。你可以创建一个新的CGMutablePathRef对象并使用多个类似于图形的路径函数的CGPath函数构造路径,或者使用CGContextCopyPath函数复制图形上下文的当前路径。有许多CGPath函数可用于创建基于简单几何形状的路径(CGPathCreateWithRect、CGPathCreateWithEllipseInRect)或基于已存在路径(CGPathCreateCopyByStrokingPath、CGPathCreateCopyDashingPath、CGPathCreateCopyByTransformingPath)。          UIKit的UIBezierPath类包装了CGPath。它提供了用于绘制某种形状路径的方法,以及用于描边、填充、存取某些当前上下文状态的设置方法。类似地,UIColor提供了用于设置当前上下文描边与填充的颜色。因此我们可以重写我们之前绘制箭头的代码     */#if 0    UIBezierPath* p = [UIBezierPath bezierPath];        [p moveToPoint:CGPointMake(100,100)];        [p addLineToPoint:CGPointMake(100, 19)];        [p setLineWidth:20];        [p stroke];        [[UIColor redColor] set];        [p removeAllPoints];        [p moveToPoint:CGPointMake(80,25)];        [p addLineToPoint:CGPointMake(100, 0)];        [p addLineToPoint:CGPointMake(120, 25)];        [p fill];        [p removeAllPoints];    [[UIColor clearColor] set];        [p moveToPoint:CGPointMake(90,101)];        [p addLineToPoint:CGPointMake(100, 90)];        [p addLineToPoint:CGPointMake(110, 101)];        [p fillWithBlendMode:kCGBlendModeClear alpha:1.0];#endif    /**     *  完成同样的工作并没有节省多少代码,但是UIBezierPath仍然还是有用的。如果你需要对象特性,UIBezierPath提供了一个便利方法:bezierPathWithRoundedRect:cornerRadius:,它可用于绘制带有圆角的矩形,如果是使用Core Graphics就相当冗长乏味了。还可以只让圆角出现在左上角和右上角。     */    #if 0    CGContextRef ctx = UIGraphicsGetCurrentContext();        CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor);        CGContextSetLineWidth(ctx, 3);        UIBezierPath *path;        path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 100, 100) byRoundingCorners:(UIRectCornerTopLeft |UIRectCornerTopRight) cornerRadii:CGSizeMake(10, 10)];        [path stroke];#endif    #if 0    /**     *  裁剪     路径的另一用处是遮蔽区域,以防对遮蔽区域进一步绘图。这种用法被称为裁剪。裁剪区域外的图形不会被绘制到。默认情况下,一个图形上下文的裁剪区域是整个图形上下文。你可在上下文中的任何地方绘图。          总的来说,裁剪区域是上下文的一个特性。与已存在的裁剪区域相交会出现新的裁剪区域。所以如果你应用了你自己的裁剪区域,稍后将它从图形上下文中移除的做法是使用CGContextSaveGState和CGContextRestoreGState函数将代码包装起来。          为了便于说明这一点,我使用裁剪而不是使用混合模式在箭头杆子上打孔的方法重写了生成箭头的代码。这样做有点小复杂,因为我们想要裁剪区域不在三角形内而在三角形外部。为了表明这一点,我们使用了一个三角形和一个矩形组成了一个组合路径。          当填充一个组合路径并使用它表示一个裁剪区域时,系统遵循以下两规则之一:          环绕规则(Winding rule)     如果边界是顺时针绘制,那么在其内部逆时针绘制的边界所包含的内容为空。如果边界是逆时针绘制,那么在其内部顺时针绘制的边界所包含的内容为空。          奇偶规则     最外层的边界代表内部都有效,都要填充;之后向内第二个边界代表它的内部无效,不需填充;如此规则继续向内寻找边界线。我们的情况非常简单,所以使用奇偶规则就很容易了。这里我们使用CGContextEOCllip设置裁剪区域然后进行绘图     */        // 在上下文裁剪区域中挖一个三角形状的孔        CGContextMoveToPoint(con, 90, 100);        CGContextAddLineToPoint(con, 100, 90);        CGContextAddLineToPoint(con, 110, 100);        CGContextClosePath(con);        CGContextAddRect(con, CGContextGetClipBoundingBox(con));        // 使用奇偶规则,裁剪区域为矩形减去三角形区域        CGContextEOClip(con);        // 绘制垂线        CGContextMoveToPoint(con, 100, 100);        CGContextAddLineToPoint(con, 100, 19);        CGContextSetLineWidth(con, 20);        CGContextStrokePath(con);        // 画红色箭头        CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);        CGContextMoveToPoint(con, 80, 25);        CGContextAddLineToPoint(con, 100, 0);         CGContextAddLineToPoint(con, 120, 25);         CGContextFillPath(con);    #endif#if 0    /**     *  渐变     渐变可以很简单也可以很复杂。一个简单的渐变(接下来要讨论的)由一端点的颜色与另一端点的颜色决定,如果在中间点加入颜色(可选),那么渐变会在上下文的两个点之间线性的绘制或在上下文的两个圆之间放射状的绘制。不能使用渐变作为路径的填充色,但可使用裁剪限制对路径形状的渐变。          我重写了绘制箭头的代码,箭杆使用了线性渐变     */            CGContextSaveGState(con);        // 在上下文裁剪区域挖一个三角形孔        CGContextMoveToPoint(con, 90, 100);        CGContextAddLineToPoint(con, 100, 90);        CGContextAddLineToPoint(con, 110, 100);        CGContextClosePath(con);        CGContextAddRect(con, CGContextGetClipBoundingBox(con));        CGContextEOClip(con);        //绘制一个垂线,让它的轮廓形状成为裁剪区域        CGContextMoveToPoint(con, 100, 100);        CGContextAddLineToPoint(con, 100, 19);        CGContextSetLineWidth(con, 20);        // 使用路径的描边版本替换图形上下文的路径        CGContextReplacePathWithStrokedPath(con);        // 对路径的描边版本实施裁剪        CGContextClip(con);        // 绘制渐变        CGFloat locs[3] = { 0.0, 0.5, 1.0 };        CGFloat colors[12] = {                0.3,0.3,0.3,0.8, // 开始颜色,透明灰                0.0,0.0,0.0,1.0, // 中间颜色,黑色                0.3,0.3,0.3,0.8 // 末尾颜色,透明灰            };        CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray();        CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3);        CGContextDrawLinearGradient(con, grad, CGPointMake(89,0), CGPointMake(111,0), 0);        CGColorSpaceRelease(sp);        CGGradientRelease(grad);        CGContextRestoreGState(con); // 完成裁剪        // 绘制红色箭头         CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);         CGContextMoveToPoint(con, 80, 25);         CGContextAddLineToPoint(con, 100, 0);         CGContextAddLineToPoint(con, 120, 25);         CGContextFillPath(con);    //调用CGContextReplacePathWithStrokedPath函数假装对当前路径描边,并使用当前线段宽度和与线段相关的上下文状态设置。但接着创建的是描边路径外部的一个新的路径。因此,相对于使用粗的线条,我们使用了一个矩形区域作为裁剪区域。        //虽然过程比较冗长但是非常的简单;我们将渐变描述为一组在一端点(0.0)和另一端点(1.0)之间连续区上的位置,以及设置与每个位置相对应的颜色。为了提亮边缘的渐变,加深中间的渐变,我使用了三个位置,黑色点的位置是0.5。为了创建渐变,还需要提供一个颜色空间。最后,我创建出了该渐变,并对裁剪区域绘制线性渐变,最后释放了颜色空间和渐变。    #endif    #if 0        /**     *  颜色与模板     在iOS中,CGColor表示颜色(具体类型为CGColorRef)。使用UIColor的colorWithCGColor:和CGColor方法可bridged cast到UIColor。          在iOS中,模板表示为CGPattern(具体类型为CGPatternRef)。你可以创建一个模板并使用它进行描边或填充。其过程是相当复杂的。作为一个非常简单的例子,我将使用红蓝相间的三角形替换箭头的三     */                CGContextSaveGState(con);        // 在上下文裁剪区域挖一个三角形孔        CGContextMoveToPoint(con, 90, 100);        CGContextAddLineToPoint(con, 100, 90);        CGContextAddLineToPoint(con, 110, 100);        CGContextClosePath(con);        CGContextAddRect(con, CGContextGetClipBoundingBox(con));        CGContextEOClip(con);        //绘制一个垂线,让它的轮廓形状成为裁剪区域        CGContextMoveToPoint(con, 100, 100);        CGContextAddLineToPoint(con, 100, 19);        CGContextSetLineWidth(con, 20);        // 使用路径的描边版本替换图形上下文的路径        CGContextReplacePathWithStrokedPath(con);        // 对路径的描边版本实施裁剪        CGContextClip(con);        // 绘制渐变        CGFloat locs[3] = { 0.0, 0.5, 1.0 };        CGFloat colors[12] = {                0.3,0.3,0.3,0.8, // 开始颜色,透明灰                0.0,0.0,0.0,1.0, // 中间颜色,黑色                0.3,0.3,0.3,0.8 // 末尾颜色,透明灰            };        CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray();        CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3);        CGContextDrawLinearGradient(con, grad, CGPointMake(89,0), CGPointMake(111,0), 0);        CGColorSpaceRelease(sp);        CGGradientRelease(grad);        CGContextRestoreGState(con); // 完成裁剪        // 绘制红色箭头        CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(NULL);        CGContextSetFillColorSpace (con, sp2);        CGColorSpaceRelease (sp2);        CGPatternCallbacks callback = {0, &drawStripes, NULL };        CGAffineTransform tr = CGAffineTransformIdentity;        CGPatternRef patt = CGPatternCreate(NULL,CGRectMake(0,0,4,4), tr, 4, 4, kCGPatternTilingConstantSpacingMinimalDistortion, true, &callback);        CGFloat alph = 1.0;        CGContextSetFillPattern(con, patt, &alph);        CGPatternRelease(patt);        CGContextMoveToPoint(con, 80, 25);        CGContextAddLineToPoint(con, 100, 0);        CGContextAddLineToPoint(con, 120, 25);        CGContextFillPath(con);    /**          代码非常冗长,但它却是一个完整的样板。现在我们从后往前分析代码: 我们调用CGContextSetFillPattern不是设置填充颜色,我们设置的是填充的模板。函数的第三个参数是一个指向CGFloat的指针,所以我们事先设置CGFloat自身。第二个参数是一个CGPatternRef对象,所以我们需要事先创建CGPatternRef,并在最后释放它。          现在开始讨论CGPatternCreate。一个模板是在一个矩形元中的绘图。我们需要矩形元的尺寸(第二个参数)以及矩形元原始点之间的间隙(第四和第五个参数)。这这种情况下,矩形元是4*4的,每一个矩形元与它的周围矩形元是紧密贴合的。我们需要提供一个应用到矩形元的变换参数(第三个参数);在这种情况下,我们不需要变换做什么工作,所以我们应用了一个恒等变换。我们应用了一个瓷砖规则(第六个参数)。我们需要声明的是颜色模板不是漏印(stencil)模板,所以参数值为true。并且我们需要提供一个指向回调函数的指针,回调函数的工作是向矩形元绘制模板。第八个参数是一个指向CGPatternCallbacks结构体的指针。这个结构体由数字0和两个指向函数的指针构成。第一个函数指针指向的函数当模板被绘制到矩形元中被调用,第二个函数指针指向的函数当模板被释放后调用。第二个函数指针我们没有指定,它的存在主要是为了内存管理的需要。但在这个简单的例子中,我们并不需要。          在你使用颜色模板调用CGContextSetFillPattern函数之前,你需要设置将应用到模板颜色空间的上下文填充颜色空间。如果你忽略这项工作,那么当你调用CGContextSetFillPattern函数时会发生错误。所以我们创建了颜色空间,设置它作为上下文的填充颜色空间,并在后面做了释放。          到这里我们仍然没有完成绘图。因为我还没有编写向矩形元中绘图的函数!绘图函数地址被表示为&drawStripes     */#endif    /**     图形上下文变换     就像UIView可以实现变换,同样图形上下文也具备这项功能。然而对图形上下文应用一个变换操作不会对已在图形上下文上的绘图产生什么影响,它只会影响到在上下文变换之后被绘制的图形,并改变被映射到图形上下文区域的坐标方式。一个图形上下文变换被称为CTM,意为“当前变换矩阵“(current transformation matrix)。          完全利用图形上下文的CTM来免于即使是简单的计算操作是很常见的。你可以使用CGContextConcatCTM函数将当前变换乘上任何CGAffineTransform,还有一些便利函数可对当前变换应用平移、缩放,旋转变换。          当你获得上下文的时候,对图形上下文的基本变换已经设置好了;这就是系统能映射上下文绘图坐标到屏幕坐标的原因。无论你对当前变换应用了什么变换,基本变换变换依然有效并且绘图继续工作。通过将你的变换代码封装到CGContextSaveGState和CGContextRestoreGState函数调用中,对基本变换应用的变换操作可以被还原。          举个例子,对于我们迄今为止使用代码绘制的向上箭头来说,已知的放置箭头的方式仅仅只有一个位置:箭头矩形框的左上角被硬编码在坐标{80,0}。这样代码很难理解、灵活性差、且很难被重用。最明智的做法是通过将所有代码中的x坐标值减去80,让箭头矩形框左上角在坐标{0,0}。事先应用一个简单的平移变换,很容易将箭头画在任何位置。为了映射坐标到箭头的左上角,我们使用下面代码:          CGContextTranslateCTM(con, 80, 0); //在坐标{0,0}处绘制箭头          旋转变换特别的有用,它可以让你在一个被旋转的方向上进行绘制而无需使用任何复杂的三角函数。然而这略有点复杂,因为旋转变换围绕的点是原点坐标。这几乎不是你所想要的,所以你先是应用了一个平移变换,为的是映射原点到你真正想绕其旋转的点。但是接着,在旋转之后,为了算出你在哪里绘图,你可能需要做一次逆向平移变换。     为了说明这个做法,我将绕箭头杆子尾部旋转多个角度重复绘制箭头,并把对箭头的绘图封装为UIImage对象。接着我们简单重复绘制UIImage对象。     */            UIGraphicsBeginImageContextWithOptions(CGSizeMake(40,100), NO, 0.0);       // CGContextRef con = UIGraphicsGetCurrentContext();        CGContextSaveGState(con);        CGContextMoveToPoint(con, 90 - 80, 100);        CGContextAddLineToPoint(con, 100 - 80, 90);        CGContextAddLineToPoint(con, 110 - 80, 100);        CGContextMoveToPoint(con, 110 - 80, 100);        CGContextAddLineToPoint(con, 100 - 80, 90);        CGContextAddLineToPoint(con, 90 - 80, 100);        CGContextClosePath(con);        CGContextAddRect(con, CGContextGetClipBoundingBox(con));        CGContextEOClip(con);        CGContextMoveToPoint(con, 100 - 80, 100);        CGContextAddLineToPoint(con, 100 - 80, 19);        CGContextSetLineWidth(con, 20);        CGContextReplacePathWithStrokedPath(con);        CGContextClip(con);        CGFloat locs[3] = { 0.0, 0.5, 1.0 };        CGFloat colors[12] = {                0.3,0.3,0.3,0.8,                0.0,0.0,0.0,1.0,                0.3,0.3,0.3,0.8            };        CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray();        CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3);        CGContextDrawLinearGradient (con, grad, CGPointMake(89 - 80,0), CGPointMake(111 - 80,0), 0);        CGColorSpaceRelease(sp);        CGGradientRelease(grad);        CGContextRestoreGState(con);        CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(NULL);        CGContextSetFillColorSpace (con, sp2);        CGColorSpaceRelease (sp2);        CGPatternCallbacks callback = {0, &drawStripes, NULL };        CGAffineTransform tr = CGAffineTransformIdentity;        CGPatternRef patt = CGPatternCreate(NULL,CGRectMake(0,0,4,4),tr,4,4,kCGPatternTilingConstantSpacingMinimalDistortion,true, &callback);        CGFloat alph = 1.0;        CGContextSetFillPattern(con, patt, &alph);        CGPatternRelease(patt);        CGContextMoveToPoint(con, 80 - 80, 25);        CGContextAddLineToPoint(con, 100 - 80, 0);        CGContextAddLineToPoint(con, 120 - 80, 25);                    CGContextFillPath(con);        UIImage* im = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();         //con = UIGraphicsGetCurrentContext();        [im drawAtPoint:CGPointMake(0,0)];         for (int i=0; i<3; i++) {                 CGContextTranslateCTM(con, 20, 100);                 CGContextRotateCTM(con, 30 * M_PI/180.0);                 CGContextTranslateCTM(con, -20, -100);                [im drawAtPoint:CGPointMake(0,0)];             }     } /** 如你所见,实际的模板绘图代码是非常简单的。唯一的复杂点在于CGPatternCreate函数必须与模板绘图函数的矩形元尺寸相同。我们知道矩形元的尺寸为4*4,所以我们用红色填充它,并接着填充它的下半部分为绿色。当这些矩形元被水平垂直平铺时,我们得到了如图8所示的条纹图案。  注意,最后图形上下文遗留下了一个不可取的状态,即填充颜色空间被设置为了一个模板颜色空间。如果稍后尝试设置填充颜色为常规颜色,就会引起错误。通常的解决方案是,使用CGContextSaveGState和CGContextRestoreGState函数将代码包起来。  你可能观察到图8的平铺效果并不与箭头的三角形内部相符合:最底部的似乎只平铺了一半蓝色。这是因为一个模板的定位并不关心你填充(描边)的形状,总的来说它只关心图形上下文。我们可以调用CGContextSetPatternPhase函数改变模板的定位。 */void drawStripes (void *info, CGContextRef con) {        // assume 4 x 4 cell        CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]);        CGContextFillRect(con, CGRectMake(0,0,4,4));        CGContextSetFillColorWithColor(con, [[UIColor blueColor] CGColor]);        CGContextFillRect(con, CGRectMake(0,0,4,2));    }


 



0 0
原创粉丝点击