- IOS绘图详解
-
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。 第一种绘图形式:在UIView的子类方法drawRect:中绘制一个蓝色圆,使用UIKit在Cocoa为我们提供的当前上下文中完成绘图任务。1<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (
void
) drawRect: (CGRect) rect { </li><li> </li><li
class
=
"alt"
>UIBezierPath* p = [UIBezierPathbezierPathWithOvalInRect:CGRectMake(
0
,
0
,
100
,
100
)]; </li><li> </li><li
class
=
"alt"
>[[UIColor blueColor] setFill]; </li><li> </li><li
class
=
"alt"
>[p fill]; </li><li> </li><li
class
=
"alt"
>} </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (
void
) drawRect: (CGRect) rect { </li><li> </li><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextAddEllipseInRect(con, CGRectMake(
0
,
0
,
100
,
100
)); </li><li> </li><li
class
=
"alt"
>CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); </li><li> </li><li
class
=
"alt"
>CGContextFillPath(con); </li><li> </li><li
class
=
"alt"
>} </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>
@interface
MyLayerDelegate : NSObject </li><li> </li><li
class
=
"alt"
>
@end
</li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>
@implementation
MyLayerDelegate </li><li> </li><li
class
=
"alt"
>- (
void
)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx { </li><li> </li><li
class
=
"alt"
> UIGraphicsPushContext(ctx); </li><li> </li><li
class
=
"alt"
> UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(
0
,
0
,
100
,
100
)]; </li><li> </li><li
class
=
"alt"
> [[UIColor blueColor] setFill]; </li><li> </li><li
class
=
"alt"
> [p fill]; </li><li> </li><li
class
=
"alt"
> UIGraphicsPopContext(); </li><li> </li><li
class
=
"alt"
>} </li><li> </li><li
class
=
"alt"
>
@end
</li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>
@interface
MyView () { </li><li> </li><li
class
=
"alt"
>MyLayerDelegate* _layerDeleagete; </li><li> </li><li
class
=
"alt"
>} </li><li> </li><li
class
=
"alt"
>
@end
</li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>MyView *myView = [[MyView alloc] initWithFrame: CGRectMake(
0
,
0
,
320
,
480
)]; </li><li> </li><li
class
=
"alt"
>CALayer *myLayer = [CALayer layer]; </li><li> </li><li
class
=
"alt"
>_layerDelegate = [[MyLayerDelegate alloc] init]; </li><li> </li><li
class
=
"alt"
>myLayer.delegate = _layerDelegate; </li><li> </li><li
class
=
"alt"
>[myView.layer addSublayer:myLayer]; </li><li> </li><li
class
=
"alt"
>[myView setNeedsDisplay];
// 调用此方法,drawLayer: inContext:方法才会被调用。 </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (
void
)drawLayer:(CALayer*)lay inContext:(CGContextRef)con { </li><li> </li><li
class
=
"alt"
>CGContextAddEllipseInRect(con, CGRectMake(
0
,
0
,
100
,
100
)); </li><li> </li><li
class
=
"alt"
>CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); </li><li> </li><li
class
=
"alt"
>CGContextFillPath(con); </li><li> </li><li
class
=
"alt"
>} </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(
100
,
100
), NO,
0
); </li><li> </li><li
class
=
"alt"
>UIBezierPath* p = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(
0
,
0
,
100
,
100
)]; </li><li> </li><li
class
=
"alt"
>[[UIColor blueColor] setFill]; </li><li> </li><li
class
=
"alt"
>[p fill]; </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(
100
,
100
), NO,
0
); </li><li> </li><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextAddEllipseInRect(con, CGRectMake(
0
,
0
,
100
,
100
)); </li><li> </li><li
class
=
"alt"
>CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); </li><li> </li><li
class
=
"alt"
>CGContextFillPath(con); </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li></ol>
12<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIImage* mars = [UIImage imageNamed:@
"Mars.png"
]; </li><li> </li><li
class
=
"alt"
>CGSize sz = [mars size]; </li><li> </li><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*
2
, sz.height), NO,
0
); </li><li> </li><li
class
=
"alt"
>[mars drawAtPoint:CGPointMake(
0
,
0
)]; </li><li> </li><li
class
=
"alt"
>[mars drawAtPoint:CGPointMake(sz.width,
0
)]; </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li><li> </li><li
class
=
"alt"
>UIImageView* iv = [[UIImageView alloc] initWithImage:im]; </li><li> </li><li
class
=
"alt"
>[self.window.rootViewController.view addSubview: iv]; </li><li> </li><li
class
=
"alt"
> iv.center = self.window.center;
</li></ol>
图1 UIImage平移处理 缩放操作:下面代码展示了如何对UIImage进行缩放操作:
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIImage* mars = [UIImage imageNamed:@
"Mars.png"
]; </li><li> </li><li
class
=
"alt"
>CGSize sz = [mars size]; </li><li> </li><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*
2
, sz.height*
2
), NO,
0
); </li><li> </li><li
class
=
"alt"
>[mars drawInRect:CGRectMake(
0
,
0
,sz.width*
2
,sz.height*
2
)]; </li><li> </li><li
class
=
"alt"
>[mars drawInRect:CGRectMake(sz.width/
2.0
, sz.height/
2.0
, sz.width, sz.height) blendMode:kCGBlendModeMultiply alpha:
1.0
]; </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li></ol>
图2 UIImage缩放处理 UIImage没有提供截取图片指定区域的功能。但通过创建一个较小的图形上下文并移动图片到一个适当的图形上下文坐标系内,指定区域内的图片就会被获取。 裁剪操作:下面代码展示了如何获取图片的右半边:
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIImage* mars = [UIImage imageNamed:@
"Mars.png"
]; </li><li> </li><li
class
=
"alt"
>CGSize sz = [mars size]; </li><li> </li><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width/
2.0
, sz.height), NO,
0
); </li><li> </li><li
class
=
"alt"
>[mars drawAtPoint:CGPointMake(-sz.width/
2.0
,
0
)]; </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li></ol>
图3 UIImage裁剪原理 CGImage常用的绘图操作<喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPgoKVUlJbWFnZbXEQ29yZSBHcmFwaGljc7Dmsb7Kx0NHSW1hZ2WjqL7fzOXA4NDNysdDR0ltYWdlUmVmo6mho8G91d+/ydLU1rG908/gu6XXqruvOiDKudPDVUlJbWFnZbXEQ0dJbWFnZcr00NS/ydLUt8POylF1YXJ0es28xqzK/b7do7u9q0NHSW1hZ2XX986qVUlJbWFnZbe9t6hpbWFnZVdpdGhDR0ltYWdlOrvyaW5pdFdpdGhDR0ltYWdlOrXEss7K/bS0vahVSUltYWdlttTP86GjCgogCgrSu7j2Q0dJbWFnZbbUz/O/ydLUyMPE47vxyKHUrcq8zbzGrNbQ1ri2qMf40/K1xM28xqyjqNKyv8nS1LvxyKHWuLaox/jT8s3itcTNvMaso6xVSUltYWdlyLSw7LK7tb2jqaGjCgogCgrPwsPmtcS0+sLr1bnKvsHLvavNvMassvC31rPJwb2w66OssqK31rHwu+bWxtTayc/Pws7EtcTX89PSwb2x36O6CjxwcmUgY2xhc3M9"brush:java;">
- UIImage* mars = [UIImage imageNamed:@"Mars.png"];
- // 抽取图片的左右半边
- 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 con = UIGraphicsGetCurrentContext();
- CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);
- CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);
- UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- // 记得释放内存,ARC在这里无效
- CGImageRelease(marsLeft);
- CGImageRelease(marsRight); 你也许发现绘出的图是上下颠倒的!图片的颠倒并不是因为被旋转了。当你创建了一个CGImage并使用CGContextDrawImage方法绘图就会引起这种问题。这主要是因为原始的本地坐标系统(坐标原点在左上角)与目标上下文(坐标原点在左下角)不匹配。有很多方法可以修复这个问题,其中一种方法就是使用CGContextDrawImage方法先将CGImage绘制到UIImage上,然后获取UIImage对应的CGImage,此时就得到了一个倒转的CGImage。当再调用CGContextDrawImage方法,我们就将倒转的图片还原回来了。实现代码如下:
1
<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGImageRef flip (CGImageRef im) { </li><li> </li><li
class
=
"alt"
>CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im)); </li><li> </li><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(sz, NO,
0
); </li><li> </li><li
class
=
"alt"
>CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(
0
,
0
, sz.width, sz.height), im); </li><li> </li><li
class
=
"alt"
>CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage]; </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li><li> </li><li
class
=
"alt"
>
return
result; </li><li> </li><li
class
=
"alt"
>} </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextDrawImage(con, CGRectMake(
0
,
0
,sz.width/
2.0
,sz.height), flip(marsLeft)); </li><li> </li><li
class
=
"alt"
>CGContextDrawImage(con, CGRectMake(sz.width,
0
,sz.width/
2.0
,sz.height), flip(marsRight)); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIImage* mars = [UIImage imageNamed:@
"Mars.png"
]; </li><li> </li><li
class
=
"alt"
>CGSize sz = [mars size]; </li><li> </li><li
class
=
"alt"
>
// 转换CGImage并使用对应的CGImage尺寸截取图片的左右部分 </li><li> </li><li class="alt">CGImageRef marsCG = [mars CGImage]; </li><li> </li><li class="alt">CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG)); </li><li> </li><li class="alt">CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG,CGRectMake(0,0,szCG.width/2.0,szCG.height)); </li><li> </li><li class="alt">CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height)); </li><li> </li><li class="alt">UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*1.5, sz.height), NO, 0); </li><li> </li><li class="alt">//剩下的和之前的代码一样,修复倒置问题 </li><li> </li><li class="alt">CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li class="alt">CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),flip(marsLeft)); </li><li> </li><li class="alt">CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),flip(marsRight)); </li><li> </li><li class="alt">UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li class="alt">UIGraphicsEndImageContext(); </li><li> </li><li class="alt">CGImageRelease(marsLeft); </li><li> </li><li class="alt">CGImageRelease(marsRight); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIImage* mars = [UIImage imageNamed:@
"Mars.png"
]; </li><li> </li><li
class
=
"alt"
>CGSize sz = [mars size]; </li><li> </li><li
class
=
"alt"
>CGImageRef marsCG = [mars CGImage]; </li><li> </li><li
class
=
"alt"
>CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG), CGImageGetHeight(marsCG)); </li><li> </li><li
class
=
"alt"
>CGImageRef marsLeft = CGImageCreateWithImageInRect(marsCG, CGRectMake(
0
,
0
,szCG.width/
2.0
,szCG.height)); </li><li> </li><li
class
=
"alt"
>CGImageRef marsRight = CGImageCreateWithImageInRect(marsCG, CGRectMake(szCG.width/
2.0
,
0
,szCG.width/
2.0
,szCG.height)); </li><li> </li><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(sz.width*
1.5
, sz.height), NO,
0
); </li><li> </li><li
class
=
"alt"
>[[UIImage imageWithCGImage:marsLeft scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(
0
,
0
)]; </li><li> </li><li
class
=
"alt"
>[[UIImage imageWithCGImage:marsRight scale:[mars scale] orientation:UIImageOrientationUp] drawAtPoint:CGPointMake(sz.width,
0
)]; </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li><li> </li><li
class
=
"alt"
>CGImageRelease(marsLeft); CGImageRelease(marsRight); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIImage* moi = [UIImage imageNamed:@
"Mars.jpeg"
]; </li><li> </li><li
class
=
"alt"
>CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage]; </li><li> </li><li
class
=
"alt"
>CIFilter* grad = [CIFilter filterWithName:@
"CIRadialGradient"
]; </li><li> </li><li
class
=
"alt"
>CIVector* center = [CIVector vectorWithX:moi.size.width /
2.0
Y:moi.size.height /
2.0
]; </li><li> </li><li
class
=
"alt"
>
// 使用setValue:forKey:方法设置滤镜属性 </li><li> </li><li class="alt">[grad setValue:center forKey:@"inputCenter"]; </li><li> </li><li class="alt">// 在指定滤镜名时提供所有滤镜键值对 </li><li> </li><li class="alt">CIFilter* dark = [CIFilter filterWithName:@"CIDarkenBlendMode" keysAndValues:@"inputImage", grad.outputImage, @"inputBackgroundImage", moi2, nil]; </li><li> </li><li class="alt">CIContext* c = [CIContext contextWithOptions:nil]; </li><li> </li><li class="alt">CGImageRef moi3 = [c createCGImage:dark.outputImage fromRect:moi2.extent]; </li><li> </li><li class="alt">UIImage* moi4 = [UIImage imageWithCGImage:moi3 scale:moi.scale orientation:moi.imageOrientation]; </li><li> </li><li class="alt">CGImageRelease(moi3); </li></ol>
图4 图片合成快照 这个例子可能没有什么吸引人的地方,因为所有一切都可以使用Core Graphics完成。除了Core Image是使用GPU处理,可能有点吸引人。Core Graphics也可以做到径向渐变并使用混合模式合成图片。但Core Image要简单得多,特别是当你有多个图片输入想重用一个滤镜链时。并且Core Image的颜色调整功能比Core Graphics更加强大。对了,Core Image还能实现自动人脸识别哦! 绘制一个UIView 绘制一个UIVIew最灵活的方式就是由它自己完成绘制。实际上你不是绘制一个UIView,你只是子类化了UIView并赋予子类绘制自己的能力。当一个UIVIew需要执行绘图操作的时, drawRect:方法就会被调用。覆盖此方法让你获得绘图操作的机会。当drawRect:方法被调用,当前图形上下文也被设置为属于视图的图形上下文。你可以使用Core Graphics或UIKit提供的方法将图形画到该上下文中。 你不应该手动调用drawRect:方法!如果你想调用drawRect:方法更新视图,只需发送setNeedsDisplay方法。这将使得drawRect:方法会在下一个适当的时间调用。当然,不要覆盖drawRect:方法除非你知道这样做绝对合法。比方说,在UIImageView子类中覆盖drawRect:方法是不合法的,你将得不到你绘制的图形。 在UIView子类的drawRect:方法中无需调用super,因为本身UIView的drawRect:方法是空的。为了提高一些绘图性能,你可以调用setNeedsDisplayInRect方法重新绘制视图的子区域,而视图的其他部分依然保持不变。 一般情况下,你不应该过早的进行优化。绘图代码可能看上去非常的繁琐,但它们是非常快的。并且iOS绘图系统自身也是非常高效,它不会频繁调用drawRect:方法,除非迫不得已(或调用了setNeedsDisplay方法)。一旦一个视图已由自己绘制完成,那么绘制的结果会被缓存下来留待重用,而不是每次重头再来。(苹果公司将缓存绘图称为视图的位图存储回填(bitmap backing store))。你可能会发现drawRect:方法中的代码在整个应用程序生命周期内只被调用了一次!事实上,将代码移到drawRect:方法中是提高性能的普遍做法。这是因为绘图引擎直接对屏幕进行渲染相对于先是脱屏渲染然后再将像素拷贝到屏幕要来的高效。 当视图的backgroundColor为nil并且opaque属性为YES,视图的背景颜色就会变成黑色。 Core Graphics上下文属性设置 当你在图形上下文中绘图时,当前图形上下文的相关属性设置将决定绘图的行为与外观。因此,绘图的一般过程是先设定好图形上下文参数,然后绘图。比方说,要画一根红线,接着画一根蓝线。那么首先需要将上下文的线条颜色属性设定为为红色,然后画红线;接着设置上下文的线条颜色属性为蓝色,再画出蓝线。表面上看,红线和蓝线是分开的,但事实上,在你画每一条线时,线条颜色却是整个上下文的属性。无论你用的是UIKit方法还是Core Graphics函数。 因为图形上下文在每一时刻都有一个确定的状态,该状态概括了图形上下文所有属性的设置。为了便于操作这些状态,图形上下文提供了一个用来持有状态的栈。调用CGContextSaveGState函数,上下文会将完整的当前状态压入栈顶;调用CGContextRestoreGState函数,上下文查找处在栈顶的状态,并设置当前上下文状态为栈顶状态。 因此一般绘图模式是:在绘图之前调用CGContextSaveGState函数保存当前状态,接着根据需要设置某些上下文状态,然后绘图,最后调用CGContextRestoreGState函数将当前状态恢复到绘图之前的状态。要注意的是,CGContextSaveGState函数和CGContextRestoreGState函数必须成对出现,否则绘图很可能出现意想不到的错误,这里有一个简单的做法避免这种情况。代码如下:
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (
void
)drawRect:(CGRect)rect { </li><li> </li><li
class
=
"alt"
>CGContextRef ctx = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextSaveGState(ctx); </li><li> </li><li
class
=
"alt"
>{ </li><li> </li><li
class
=
"alt"
>
// 绘图代码 </li><li> </li><li class="alt">} </li><li> </li><li class="alt">CGContextRestoreGState(ctx); </li><li> </li><li class="alt"> } </li></ol>
图5 CGContextClearRect函数的应用 如图5,在左边的蓝色正方形被挖去部分留为黑色,然而在右边的蓝色正方形也被挖去部分留为透明。但这两个正方形都是UIView子类的实例,采用相同的绘图代码!不同之处在于视图的背景颜色,左边的正方形的背景颜色在nib文件中 但是这却完全改变了CGContextClearRect函数的效果。UIView子类的drawRect:方法看起来像这样:
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor); </li><li> </li><li
class
=
"alt"
>CGContextFillRect(con, rect); </li><li> </li><li
class
=
"alt"
>CGContextClearRect(con, CGRectMake(
0
,
0
,
30
,
30
)); </li></ol>
图6 一个简单的路径绘图
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>
// 绘制一个黑色的垂直黑色线,作为箭头的杆子 </li><li> </li><li class="alt">CGContextMoveToPoint(con, 100, 100); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 19); </li><li> </li><li class="alt">CGContextSetLineWidth(con, 20); </li><li> </li><li class="alt">CGContextStrokePath(con); </li><li> </li><li class="alt">// 绘制一个红色三角形箭头 </li><li> </li><li class="alt">CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]); </li><li> </li><li class="alt">CGContextMoveToPoint(con, 80, 25); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 0); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 120, 25); </li><li> </li><li class="alt">CGContextFillPath(con); </li><li> </li><li class="alt">// 从箭头杆子上裁掉一个三角形,使用清除混合模式 </li><li> </li><li class="alt">CGContextMoveToPoint(con, 90, 101); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 90); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 110, 101); </li><li> </li><li class="alt">CGContextSetBlendMode(con, kCGBlendModeClear); </li><li> </li><li class="alt">CGContextFillPath(con); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>UIBezierPath* p = [UIBezierPath bezierPath]; </li><li> </li><li
class
=
"alt"
>[p moveToPoint:CGPointMake(
100
,
100
)]; </li><li> </li><li
class
=
"alt"
>[p addLineToPoint:CGPointMake(
100
,
19
)]; </li><li> </li><li
class
=
"alt"
>[p setLineWidth:
20
]; </li><li> </li><li
class
=
"alt"
>[p stroke]; </li><li> </li><li
class
=
"alt"
>[[UIColor redColor] set]; </li><li> </li><li
class
=
"alt"
>[p removeAllPoints]; </li><li> </li><li
class
=
"alt"
>[p moveToPoint:CGPointMake(
80
,
25
)]; </li><li> </li><li
class
=
"alt"
>[p addLineToPoint:CGPointMake(
100
,
0
)]; </li><li> </li><li
class
=
"alt"
>[p addLineToPoint:CGPointMake(
120
,
25
)]; </li><li> </li><li
class
=
"alt"
>[p fill]; </li><li> </li><li
class
=
"alt"
>[p removeAllPoints]; </li><li> </li><li
class
=
"alt"
>[p moveToPoint:CGPointMake(
90
,
101
)]; </li><li> </li><li
class
=
"alt"
>[p addLineToPoint:CGPointMake(
100
,
90
)]; </li><li> </li><li
class
=
"alt"
>[p addLineToPoint:CGPointMake(
110
,
101
)]; </li><li> </li><li
class
=
"alt"
>[p fillWithBlendMode:kCGBlendModeClear alpha:
1.0
]; </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (
void
)drawRect:(CGRect)rect { </li><li> </li><li
class
=
"alt"
> CGContextRef ctx = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
> CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor); </li><li> </li><li
class
=
"alt"
> CGContextSetLineWidth(ctx,
3
); </li><li> </li><li
class
=
"alt"
> UIBezierPath *path; </li><li> </li><li
class
=
"alt"
> path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(
100
,
100
,
100
,
100
) byRoundingCorners:(UIRectCornerTopLeft |UIRectCornerTopRight) cornerRadii:CGSizeMake(
10
,
10
)]; </li><li> </li><li
class
=
"alt"
> [path stroke]; </li><li> </li><li
class
=
"alt"
>} </li></ol>
图7 左右圆角矩形 裁剪 路径的另一用处是遮蔽区域,以防对遮蔽区域进一步绘图。这种用法被称为裁剪。裁剪区域外的图形不会被绘制到。默认情况下,一个图形上下文的裁剪区域是整个图形上下文。你可在上下文中的任何地方绘图。 总的来说,裁剪区域是上下文的一个特性。与已存在的裁剪区域相交会出现新的裁剪区域。所以如果你应用了你自己的裁剪区域,稍后将它从图形上下文中移除的做法是使用CGContextSaveGState和CGContextRestoreGState函数将代码包装起来。 为了便于说明这一点,我使用裁剪而不是使用混合模式在箭头杆子上打孔的方法重写了生成箭头的代码。这样做有点小复杂,因为我们想要裁剪区域不在三角形内而在三角形外部。为了表明这一点,我们使用了一个三角形和一个矩形组成了一个组合路径。 当填充一个组合路径并使用它表示一个裁剪区域时,系统遵循以下两规则之一: 环绕规则(Winding rule) 如果边界是顺时针绘制,那么在其内部逆时针绘制的边界所包含的内容为空。如果边界是逆时针绘制,那么在其内部顺时针绘制的边界所包含的内容为空。 奇偶规则 最外层的边界代表内部都有效,都要填充;之后向内第二个边界代表它的内部无效,不需填充;如此规则继续向内寻找边界线。我们的情况非常简单,所以使用奇偶规则就很容易了。这里我们使用CGContextEOCllip设置裁剪区域然后进行绘图。(如果不是很明白,可以参见这篇文章:五种方法绘制有孔的2d形状)
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>
// 在上下文裁剪区域中挖一个三角形状的孔 </li><li> </li><li class="alt">CGContextMoveToPoint(con, 90, 100); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 90); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 110, 100); </li><li> </li><li class="alt">CGContextClosePath(con); </li><li> </li><li class="alt">CGContextAddRect(con, CGContextGetClipBoundingBox(con)); </li><li> </li><li class="alt">// 使用奇偶规则,裁剪区域为矩形减去三角形区域 </li><li> </li><li class="alt">CGContextEOClip(con); </li><li> </li><li class="alt">// 绘制垂线 </li><li> </li><li class="alt">CGContextMoveToPoint(con, 100, 100); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 19); </li><li> </li><li class="alt">CGContextSetLineWidth(con, 20); </li><li> </li><li class="alt">CGContextStrokePath(con); </li><li> </li><li class="alt">// 画红色箭头 </li><li> </li><li class="alt">CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]); </li><li> </li><li class="alt">CGContextMoveToPoint(con, 80, 25); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 0); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 120, 25); </li><li> </li><li class="alt">CGContextFillPath(con); </li></ol>
图8 箭头杆子渐变
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextSaveGState(con); </li><li> </li><li
class
=
"alt"
>
// 在上下文裁剪区域挖一个三角形孔 </li><li> </li><li class="alt">CGContextMoveToPoint(con, 90, 100); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 90); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 110, 100); </li><li> </li><li class="alt">CGContextClosePath(con); </li><li> </li><li class="alt">CGContextAddRect(con, CGContextGetClipBoundingBox(con)); </li><li> </li><li class="alt">CGContextEOClip(con); </li><li> </li><li class="alt">//绘制一个垂线,让它的轮廓形状成为裁剪区域 </li><li> </li><li class="alt">CGContextMoveToPoint(con, 100, 100); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 19); </li><li> </li><li class="alt">CGContextSetLineWidth(con, 20); </li><li> </li><li class="alt">// 使用路径的描边版本替换图形上下文的路径 </li><li> </li><li class="alt">CGContextReplacePathWithStrokedPath(con); </li><li> </li><li class="alt">// 对路径的描边版本实施裁剪 </li><li> </li><li class="alt">CGContextClip(con); </li><li> </li><li class="alt">// 绘制渐变 </li><li> </li><li class="alt">CGFloat locs[3] = { 0.0, 0.5, 1.0 }; </li><li> </li><li class="alt">CGFloat colors[12] = { </li><li> </li><li class="alt">0.3,0.3,0.3,0.8, // 开始颜色,透明灰 </li><li> </li><li class="alt">0.0,0.0,0.0,1.0, // 中间颜色,黑色 </li><li> </li><li class="alt">0.3,0.3,0.3,0.8 // 末尾颜色,透明灰 </li><li> </li><li class="alt">}; </li><li> </li><li class="alt">CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray(); </li><li> </li><li class="alt">CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs, 3); </li><li> </li><li class="alt">CGContextDrawLinearGradient(con, grad, CGPointMake(89,0), CGPointMake(111,0), 0); </li><li> </li><li class="alt">CGColorSpaceRelease(sp); </li><li> </li><li class="alt">CGGradientRelease(grad); </li><li> </li><li class="alt">CGContextRestoreGState(con); // 完成裁剪 </li><li> </li><li class="alt">// 绘制红色箭头 </li><li> </li><li class="alt">CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]); </li><li> </li><li class="alt">CGContextMoveToPoint(con, 80, 25); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 100, 0); </li><li> </li><li class="alt">CGContextAddLineToPoint(con, 120, 25); </li><li> </li><li class="alt">CGContextFillPath(con); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(NULL); </li><li> </li><li
class
=
"alt"
>CGContextSetFillColorSpace (con, sp2); </li><li> </li><li
class
=
"alt"
>CGColorSpaceRelease (sp2); </li><li> </li><li
class
=
"alt"
>CGPatternCallbacks callback = {
0
, &drawStripes, NULL }; </li><li> </li><li
class
=
"alt"
>CGAffineTransform tr = CGAffineTransformIdentity; </li><li> </li><li
class
=
"alt"
>CGPatternRef patt = CGPatternCreate(NULL,CGRectMake(
0
,
0
,
4
,
4
), tr,
4
,
4
, kCGPatternTilingConstantSpacingMinimalDistortion,
true
, &callback); </li><li> </li><li
class
=
"alt"
>CGFloat alph =
1.0
; </li><li> </li><li
class
=
"alt"
>CGContextSetFillPattern(con, patt, &alph); </li><li> </li><li
class
=
"alt"
>CGPatternRelease(patt); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>
void
drawStripes (
void
*info, CGContextRef con) { </li><li> </li><li
class
=
"alt"
>
// assume 4 x 4 cell </li><li> </li><li class="alt">CGContextSetFillColorWithColor(con, [[UIColor redColor] CGColor]); </li><li> </li><li class="alt">CGContextFillRect(con, CGRectMake(0,0,4,4)); </li><li> </li><li class="alt">CGContextSetFillColorWithColor(con, [[UIColor blueColor] CGColor]); </li><li> </li><li class="alt">CGContextFillRect(con, CGRectMake(0,0,4,2)); </li><li> </li><li class="alt">} </li></ol>
图9 模板填充 如你所见,实际的模板绘图代码是非常简单的。唯一的复杂点在于CGPatternCreate函数必须与模板绘图函数的矩形元尺寸相同。我们知道矩形元的尺寸为4*4,所以我们用红色填充它,并接着填充它的下半部分为绿色。当这些矩形元被水平垂直平铺时,我们得到了如图8所示的条纹图案。 注意,最后图形上下文遗留下了一个不可取的状态,即填充颜色空间被设置为了一个模板颜色空间。如果稍后尝试设置填充颜色为常规颜色,就会引起错误。通常的解决方案是,使用CGContextSaveGState和CGContextRestoreGState函数将代码包起来。 你可能观察到图8的平铺效果并不与箭头的三角形内部相符合:最底部的似乎只平铺了一半蓝色。这是因为一个模板的定位并不关心你填充(描边)的形状,总的来说它只关心图形上下文。我们可以调用CGContextSetPatternPhase函数改变模板的定位。 图形上下文变换 就像UIView可以实现变换,同样图形上下文也具备这项功能。然而对图形上下文应用一个变换操作不会对已在图形上下文上的绘图产生什么影响,它只会影响到在上下文变换之后被绘制的图形,并改变被映射到图形上下文区域的坐标方式。一个图形上下文变换被称为CTM,意为“当前变换矩阵“(current transformation matrix)。 完全利用图形上下文的CTM来免于即使是简单的计算操作是很常见的。你可以使用CGContextConcatCTM函数将当前变换乘上任何CGAffineTransform,还有一些便利函数可对当前变换应用平移、缩放,旋转变换。 当你获得上下文的时候,对图形上下文的基本变换已经设置好了;这就是系统能映射上下文绘图坐标到屏幕坐标的原因。无论你对当前变换应用了什么变换,基本变换变换依然有效并且绘图继续工作。通过将你的变换代码封装到CGContextSaveGState和CGContextRestoreGState函数调用中,对基本变换应用的变换操作可以被还原。 举个例子,对于我们迄今为止使用代码绘制的向上箭头来说,已知的放置箭头的方式仅仅只有一个位置:箭头矩形框的左上角被硬编码在坐标{80,0}。这样代码很难理解、灵活性差、且很难被重用。最明智的做法是通过将所有代码中的x坐标值减去80,让箭头矩形框左上角在坐标{0,0}。事先应用一个简单的平移变换,很容易将箭头画在任何位置。为了映射坐标到箭头的左上角,我们使用下面代码: CGContextTranslateCTM(con, 80, 0); //在坐标{0,0}处绘制箭头 旋转变换特别的有用,它可以让你在一个被旋转的方向上进行绘制而无需使用任何复杂的三角函数。然而这略有点复杂,因为旋转变换围绕的点是原点坐标。这几乎不是你所想要的,所以你先是应用了一个平移变换,为的是映射原点到你真正想绕其旋转的点。但是接着,在旋转之后,为了算出你在哪里绘图,你可能需要做一次逆向平移变换。 为了说明这个做法,我将绕箭头杆子尾部旋转多个角度重复绘制箭头,并把对箭头的绘图封装为UIImage对象。接着我们简单重复绘制UIImage对象。 具体代码如下:
12<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (
void
)drawRect:(CGRect)rect { </li><li> </li><li
class
=
"alt"
>UIGraphicsBeginImageContextWithOptions(CGSizeMake(
40
,
100
), NO,
0.0
); </li><li> </li><li
class
=
"alt"
>CGContextRef con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextSaveGState(con); </li><li> </li><li
class
=
"alt"
>CGContextMoveToPoint(con,
90
-
80
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
100
-
80
,
90
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
110
-
80
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextMoveToPoint(con,
110
-
80
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
100
-
80
,
90
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
90
-
80
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextClosePath(con); </li><li> </li><li
class
=
"alt"
>CGContextAddRect(con, CGContextGetClipBoundingBox(con)); </li><li> </li><li
class
=
"alt"
>CGContextEOClip(con); </li><li> </li><li
class
=
"alt"
>CGContextMoveToPoint(con,
100
-
80
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
100
-
80
,
19
); </li><li> </li><li
class
=
"alt"
>CGContextSetLineWidth(con,
20
); </li><li> </li><li
class
=
"alt"
>CGContextReplacePathWithStrokedPath(con); </li><li> </li><li
class
=
"alt"
>CGContextClip(con); </li><li> </li><li
class
=
"alt"
>CGFloat locs[
3
] = {
0.0
,
0.5
,
1.0
}; </li><li> </li><li
class
=
"alt"
>CGFloat colors[
12
] = { </li><li> </li><li
class
=
"alt"
>
0.3
,
0.3
,
0.3
,
0.8
, </li><li> </li><li
class
=
"alt"
>
0.0
,
0.0
,
0.0
,
1.0
, </li><li> </li><li
class
=
"alt"
>
0.3
,
0.3
,
0.3
,
0.8
</li><li> </li><li
class
=
"alt"
>}; </li><li> </li><li
class
=
"alt"
>CGColorSpaceRef sp = CGColorSpaceCreateDeviceGray(); </li><li> </li><li
class
=
"alt"
>CGGradientRef grad = CGGradientCreateWithColorComponents (sp, colors, locs,
3
); </li><li> </li><li
class
=
"alt"
>CGContextDrawLinearGradient (con, grad, CGPointMake(
89
-
80
,
0
), CGPointMake(
111
-
80
,
0
),
0
); </li><li> </li><li
class
=
"alt"
>CGColorSpaceRelease(sp); </li><li> </li><li
class
=
"alt"
>CGGradientRelease(grad); </li><li> </li><li
class
=
"alt"
>CGContextRestoreGState(con); </li><li> </li><li
class
=
"alt"
>CGColorSpaceRef sp2 = CGColorSpaceCreatePattern(NULL); </li><li> </li><li
class
=
"alt"
>CGContextSetFillColorSpace (con, sp2); </li><li> </li><li
class
=
"alt"
>CGColorSpaceRelease (sp2); </li><li> </li><li
class
=
"alt"
>CGPatternCallbacks callback = {
0
, &drawStripes, NULL }; </li><li> </li><li
class
=
"alt"
>CGAffineTransform tr = CGAffineTransformIdentity; </li><li> </li><li
class
=
"alt"
>CGPatternRef patt = CGPatternCreate(NULL,CGRectMake(
0
,
0
,
4
,
4
),tr,
4
,
4
,kCGPatternTilingConstantSpacingMinimalDistortion,
true
, &callback); </li><li> </li><li
class
=
"alt"
>CGFloat alph =
1.0
; </li><li> </li><li
class
=
"alt"
>CGContextSetFillPattern(con, patt, &alph); </li><li> </li><li
class
=
"alt"
>CGPatternRelease(patt); </li><li> </li><li
class
=
"alt"
>CGContextMoveToPoint(con,
80
-
80
,
25
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
100
-
80
,
0
); </li><li> </li><li
class
=
"alt"
>CGContextAddLineToPoint(con,
120
-
80
,
25
); </li><li> </li><li
class
=
"alt"
>CGContextFillPath(con); </li><li> </li><li
class
=
"alt"
>UIImage* im = UIGraphicsGetImageFromCurrentImageContext(); </li><li> </li><li
class
=
"alt"
>UIGraphicsEndImageContext(); </li><li> </li><li
class
=
"alt"
>con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>[im drawAtPoint:CGPointMake(
0
,
0
)]; </li><li> </li><li
class
=
"alt"
>
for
(
int
i=
0
; i<
3
; i++) { </li><li> </li><li
class
=
"alt"
>CGContextTranslateCTM(con,
20
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextRotateCTM(con,
30
* M_PI/
180.0
); </li><li> </li><li
class
=
"alt"
>CGContextTranslateCTM(con, -
20
, -
100
); </li><li> </li><li
class
=
"alt"
>[im drawAtPoint:CGPointMake(
0
,
0
)]; </li><li> </li><li
class
=
"alt"
>} </li><li> </li><li
class
=
"alt"
>}
</li></ol>
图10 使用CTM旋转变换 变换有多个方法解决我们早期使用CGContextDrawImage函数遇到的倒置问题。相对于逆向绘图,我们选择逆向我们绘图的上下文。实质上,我们对上下文坐标系统应用了一个“倒置”变换。你自上而下移动上下文,接着你通过应用一个让y坐标乘以-1的缩放变换逆向y坐标的方向。
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextTranslateCTM(con,
0
, theHeight); </li><li> </li><li
class
=
"alt"
>CGContextScaleCTM(con,
1.0
, -
1.0
); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextTranslateCTM(con,
0
, sz.height);
// sz为[mars size] </li><li> </li><li class="alt">CGContextScaleCTM(con, 1.0, -1.0); </li><li> </li><li class="alt">CGContextDrawImage(con, CGRectMake(0, 0, sz.width/2.0, sz.height), marsLeft); </li><li> </li><li class="alt">CGContextDrawImage(con, CGRectMake(b.size.width-sz.width/2.0, 0, sz.width/2.0, sz.height),marsRight); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextSetShadow(con, CGSizeMake(
7
,
7
),
12
); </li><li> </li><li
class
=
"alt"
>[im drawAtPoint:CGPointMake(
0
,
0
)]; </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>con = UIGraphicsGetCurrentContext(); </li><li> </li><li
class
=
"alt"
>CGContextSetShadow(con, CGSizeMake(
7
,
7
),
12
); </li><li> </li><li
class
=
"alt"
>CGContextBeginTransparencyLayer(con, NULL); </li><li> </li><li
class
=
"alt"
>[im drawAtPoint:CGPointMake(
0
,
0
)]; </li><li> </li><li
class
=
"alt"
>
for
(
int
i=
0
; i<
3
; i++) { </li><li> </li><li
class
=
"alt"
>CGContextTranslateCTM(con,
20
,
100
); </li><li> </li><li
class
=
"alt"
>CGContextRotateCTM(con,
30
* M_PI/
180.0
); </li><li> </li><li
class
=
"alt"
>CGContextTranslateCTM(con, -
20
, -
100
); </li><li> </li><li
class
=
"alt"
>[im drawAtPoint:CGPointMake(
0
,
0
)]; </li><li> </li><li
class
=
"alt"
>} </li><li> </li><li
class
=
"alt"
>
// 在调用了CGContextEndTransparencyLayer函数之后, </li><li> </li><li class="alt">// 图层内容会在应用全局alpha和上下文阴影状态之后被合成到上下文中 </li><li> </li><li class="alt">CGContextEndTransparencyLayer(con); </li></ol>
图11 阴影效果 点与像素 一个点是由xy坐标描述的一个无穷小量的位置。通过指定点实现在图形上下文中的绘图。我们并没有关心设备的分辨率,因为Core Graphics已经精细地将绘图映射到物理输出设备(基于CTM、反锯齿和平滑技术)。因此,文章之前的讨论只关心图形上下文的点,不关注点与屏幕像素的关系。 然而像素是真实存在的。一个像素是真实世界中一个具有完整物理尺寸的显示单元。整数的点实际上介于像素之间。在单分辨率设备上,这可能会让人感到迷惑。比方说,如果使用线宽为1的线条对一个整数坐标的垂直路径描边,那么线条将会被分为两半,分别落在路径的两侧。所以在单分辨率设备上线宽会变成2px(因为设备无法表示半个像素)。
图12 整数的点坐标与偏移0.5点的坐标对应的描边处理 当你遇到显示效果不佳的时,可能会被建议通过对坐标增减0.5让它在像素中居中。这个建议可能有效,如图11。但它只是做了一些头脑简单的假设。一个复杂的做法是获得UIView的contentScaleFactor属性。这个值为1.0或2.0,所以你可以除以这个属性值得到从像素到点的转换。还可以想想用最精确的方式绘制一条水平或垂直的线条的方式不是描边路径,而是填充路径。使用这种方法UIView的子类代码将可以在任何设备上绘制一条完美的1px宽的垂线,代码如下:
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>CGContextFillRect(con, CGRectMake(
100
,
0
,
1.0
/self.contentScaleFactor,
100
)); </li></ol>
1<ol
class
=
"dp-c"
><li
class
=
"alt"
>- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { </li><li> </li><li
class
=
"alt"
> self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; </li><li> </li><li
class
=
"alt"
> self.window.rootViewController = [UIViewController
new
]; </li><li> </li><li
class
=
"alt"
> MyView* mv =[[MyView alloc] initWithFrame:CGRectMake(
0
,
0
, self.window.bounds.size.width -
50
,
150
)]; </li><li> </li><li
class
=
"alt"
> mv.center = self.window.center; </li><li> </li><li
class
=
"alt"
> [self.window.rootViewController.view addSubview: mv]; </li><li> </li><li
class
=
"alt"
> mv.opaque = NO; </li><li> </li><li
class
=
"alt"
> mv.tag =
111
;
// so I can get a reference to this view later </li><li> </li><li class="alt"> [self performSelector:@selector(resize:) withObject:nil afterDelay:0.1]; </li><li> </li><li class="alt"> self.window.backgroundColor = [UIColor whiteColor]; </li><li> </li><li class="alt"> [self.window makeKeyAndVisible]; </li><li> </li><li class="alt"> return YES; </li><li> </li><li class="alt">} </li></ol>
图13 内容自动伸展 可是早晚drawRect:方法会被调用,绘图将按照drawRect:方法中的代码被刷新。代码不会将箭头绘制在相对于视图边界的高度。它是在一个固定的高度。因此箭头会伸展,而且会在以后某个时间返回到原始的尺寸。 通常我们的视图的contentMode属性需要与视图绘制自己的方式一致。假设我们的drawRect:方法中的代码让箭头的尺寸和位置相对于视图的边界原点,即它的左上方。所以我们可以设置它的contentMode为UIViewContentModeTopLeft。又或者,我们可以将contentMode设置为UIVIewContentModeRedraw,这将引起缓存内容的自动缩放和重定位被关闭,最终结果是视图的setNeedsDisplay方法将被调用,触发drawRect:方法重绘视图内容。 在另一方面,如果一个视图只是暂时被调整大小。假设是作为动画的一部分,那么伸缩行为正是你所想要的。假设我们的动画是想要让视图变大然后还原回原始大小以达到作为吸引用户的一种手段。这就需要视图伸缩的时候视图的内容也跟着伸缩,正确的contentMode的值是UIViewContentModeScaleToFill,被伸缩的内容仅仅是视图内容的一副缓存图片,所以它运行起来十分的高效
。