IOS绘图探索
参考: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
- (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) ;
}
运行结果:
#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;
}
运行结果:
- (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);
}
运行结果
////这个方法实现会把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];
- (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属性,它不知道图片文件的尺寸是否为两倍!所以当调用UIImage的CGImage方法,你不能假定所获得的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));
}