Core Graphic(二):context详解

上一篇介绍了CG的历史

context是Quartz的核心概念,在用CG进行画图的时候,必须和context打交道,所以必须要知道context是什么,干什么以及为什么要有context。

我们使用CG最基本的操作就是创建路径,路径是一个用数学描述的图形形状,路径可以是矩形的,圆形的,牛仔帽形甚至是一个泰姬陵的形状。路径内可以用颜色进行填充,每一个像素点都可以被设置成特定的颜色。路径也可以是一个轮廓。这个有点像是字帖。下面是三幅图,一副是一个帽子的轮廓,一副填充蓝色的帽子,另一副是填充黄色。


正如你所见,上面的轮廓还是挺复杂的。它可以被涂上任何颜色。也可以设置线条的样式,可以设置成粗线条也可以是细线条。线条的末端还能设置成矩形或者圆形。等等,还有很多的属性可以设置。

你查看CG的API后会发现,没有一个函数的参数是包含所有属性的。

CGStrokePath (path, fillColor, strokeColor, lineWidth, dashPattern, bloodType, endCap)

取而代之的,是下面这个函数

void CGContextStrokePath(CGContextRef c)

其他的属性是来自于哪里呢?都是从context中获得的。

context属性

context拥有许多全局的绘画状态。他们之间都是独立的。

  • current state
  • current fill and stroke colors
  • line width and pattern
  • line cap and join(miter)style
  • alpha(transparency) ,antialiasing and blend mode
  • shadows
  • transformation matrix
  • text attribuates(font, size, matrix)
  • esoteric things like line flatness and interpolation quality
  • and more

有非常多的属性。事实上CG的所有属性并没有被文档化,所以很多属性都是很隐性的。不同的conext(image,PDf,etc)拥有不同的属性。

当CG要开始画东西时,例如“fill a path”,它只会去获取需要的属性。同样的代码可能因为不同的context就会得到截然不同的效果。从某方面来说,这是非常强大的。但从另一方面来看,因为context持有的属性都是全局的,所以很容易将属性搞得一团糟。

如果你的代码是这样的:

 draw orange square:
  set color to orange in the current context
  fill a rectagle

这个时候画出来的是一个橘色的矩形

 draw red valentine:
   set color to red in the current context
   fill a valentine

ok,一个红心。这个时候你想将这个代码加到第一段代码中:

  draw orange square:
    set color to orange in the current context
    draw red valentine
    fill a rectangle

这个时候画出来的矩形就变成了红色,为什么?因为画红心的代码中将当前的color设置成了红色。之前的橘色被红色冲掉了,如何避免这样的bug呢?

这里有两种方法:一种是将当前的颜色进行存储(橘色),然后画红心(红色),然后再将橘色重新赋值回去,画矩形。这种方式可以解决上述问题,但是这样做的坏处是,如果context的属性较多,层级较深,这样保存很容易混乱。当然你会想到一个方法就是获取当前context的属性,但是不好意思,CG没有提供getter方法来获取context属性。

context栈

对于上述的问题一个可行的方法就在改变全局属性之前保存整个context。保存context,调整线条属性,颜色属性,绘画。然后恢复context。CG提供了一个保存和恢复context的API。保存context其实就是保存全局属性。实际上,后端是用一个栈结构来保存这些属性的。

当保存context时,就会将当前所有的属性push到栈中。当恢复context时,就会pop出上次的属性,然后当前context也就恢复到上次的状态。下面就来演示下如何用这种方面修复上述的bug:

draw red valentine:
  save graphic state
  set color to red in the current context
  fill a valentine
  restore graphics state

然后,整个流程就像下面这样:

 set color to orange in the current context
 save graphics state
 set colot to red in the current context
 fill a valenine
 restore graphic state
 fill a rectangle

下面用图示来演示上述代码:


Core Graphics API

到此为止,上面都是用伪代码在讲述,究竟CG的代码是什么样的呢?它们看上去有点点负责。Core graphics 风格和 Core Fundation API一样。其中比较让人头疼的是定义指针类型。但是在CG中,凡是“Ref”结尾的,例如CGContextRef活着CGColorRef。这些在实际的定义中就是指针类型的变量。

正确的形式

CGContextRef currentContext = ....; // actually a pointer

错误的形式
CGContextRef *currentContext = ...; // pointer to a pointer

在绘画的时候,很少自己去创建一个context,大多数情况下是获得当前的context:

CGContextRef context = UIGraphicsGetCurrentContext ();

在Mac中这样获取:

CGContextRef context = [NSGraphicsContext.currentContext graphicsPort] //before 10.10
CGContextRef context = [NSGraphicsContext.currentContext CGContext] //10.10

Draw

因为CG是一套C语言实现的面向对象API,函数中传入的第一个参数是“对象”。 例如,下面是画一个矩形轮廓的代码:

CGContextRef context = ...;  // See above for the proper call for your platform
CGRect bounds = someView.bounds;
CGContextStrokeRect (context, bounds);

CGCotextStrokeRect将CGContextRef设置为第一个参数。这个函数的目的是画一个矩形轮廓。如果context是一个image类型,或者是要渲染当前屏幕的,则这个矩形就会去获取当前全局变量中的属性(线宽,颜色,图案等)。如果这个context是PDF,则就会生成特定的指令文件让PDf viewer来解析。

Context

GrafDemo是一个CG的Demo,它会讲CG的方方面面都做一个演示。代码在Github中。

GrafDemo包含了一个NSView,画了一个边框为蓝色,填充是绿色的圆圈。白色的背景边框是黑色。


上面有两个版本的图形,一个是正确的Context属性,另一个不是。注意那个错误的图形。白色背景的边框被设置成了蓝色。Demo中同时含有OC和Swift版本的。

获取当前view上的context很简单,下面是Mac中的获取方法:

- (CGContextRef) currentContext {
    return [NSGraphicsContext.currentContext graphicsPort];
} // currentContext

在view的drawRect方法中,实现绘制图形背景线条的函数。 [code]

绘制背景和轮廓的函数也很简单:

- (void) drawSloppily {
    CGContextRef context = self.currentContext;

    CGContextSetRGBStrokeColor (context, 0.0, 0.0, 0.0, 1.0); // Black
    CGContextSetRGBFillColor (context, 1.0, 1.0, 1.0, 1.0); // White

    [self drawSloppyBackground];
    [self drawSloppyContents];
    [self drawSloppyBorder];

} // drawSloppily

- (void) drawSloppyBackground {
    CGContextFillRect (self.currentContext, self.bounds);
} // drawSloppyBackground

- (void) drawSloppyBorder {
    CGContextStrokeRect (self.currentContext, self.bounds);
} // drawSloppyBorder

下面的代码是在drawRect中调用的,但是,这里存在一个问题:

- (void) drawSloppyContents {
    CGContextRef context = self.currentContext;

    CGRect innerRect = CGRectInset (self.bounds, 20, 20);

    CGContextSetRGBFillColor (context, 0.0, 1.0, 0.0, 1.0); // Green
    CGContextFillEllipseInRect (context, innerRect);

    CGContextSetRGBStrokeColor (context, 0.0, 0.0, 1.0, 1.0); // Blue
    CGContextSetLineWidth (context, 6.0);
    CGContextStrokeEllipseInRect (context, innerRect);

} // drawSloppyContents

注意,上述代码中,讲context中的一些属性进行了改变,这些都是全局属性。所以会影响后面的背景边框颜色。

Push&pull

修复上面bug的一个方法就是,在绘制矩形之前,保存context,绘制之后,恢复context。CGContextSaveGState将当前的contextpush到栈中。CGContextRestoreGState当栈顶的context恢复到当前的context中。

下面是一个修复后的代码版本:

- (void) drawNiceContents {
    CGContextRef context = self.currentContext;

    CGContextSaveGState (context); {
        CGRect innerRect = CGRectInset (self.bounds, 20, 20);

        CGContextSetLineWidth (context, 6.0);

        CGContextSetRGBFillColor (context, 0.0, 1.0, 0.0, 1.0); // Green
        CGContextFillEllipseInRect (context, innerRect);

        CGContextSetRGBStrokeColor (context, 0.0, 0.0, 1.0, 1.0); // Blue
        CGContextStrokeEllipseInRect (context, innerRect);

    } CGContextRestoreGState (context);

} // drawNiceContents

代码中的{}符号,只是个人的编码风格,是为了让代码看上去更加有逻辑性,加不加对功能都没有任何影响。

Swift

CG的代码在OC和swift中使用几乎是一样的,唯一不同的是末尾不需要分号。下面是swift版本的代码:

func drawSloppyContents() {
    let innerRect = CGRectInset(bounds, 20.0, 20.0)

    CGContextSetRGBFillColor (currentContext, 0.0, 1.0, 0.0, 1.0) // Green
    CGContextFillEllipseInRect (currentContext, innerRect)

    CGContextSetRGBStrokeColor (currentContext, 0.0, 0.0, 1.0, 1.0) // Blue
    CGContextSetLineWidth (currentContext, 6.0)
    CGContextStrokeEllipseInRect (currentContext, innerRect)
}

在OC中{}符号不适用于swift,所以这里换作函数闭包的形式来保持代码的逻辑性: 

private func saveGState(drawStuff: () -> ()) -> () {
    CGContextSaveGState (currentContext)
    drawStuff()
    CGContextRestoreGState (currentContext)
}

func drawNiceContents() {
    saveGState {
        let innerRect = CGRectInset(self.bounds, 20.0, 20.0)

        CGContextSetRGBFillColor (self.currentContext, 0.0, 1.0, 0.0, 1.0) // Green
        CGContextFillEllipseInRect (self.currentContext, innerRect)

        CGContextSetRGBStrokeColor (self.currentContext, 0.0, 0.0, 1.0, 1.0) // Blue
        CGContextSetLineWidth (self.currentContext, 6.0)
        CGContextStrokeEllipseInRect (self.currentContext, innerRect)
    }
}

获取context

swift中获取context非常简单,也不需要用到Ref指针这些概念:

let context = UIGraphicsGetCurrentContext()

UIGraphicsGetCurrentContext()在OC中是返回一个CGContextRef类型的变量。

OSX 10.10中的获取方法: 

let context = NSGraphicsContext.currentContext?.CGContext

10.9的获取就稍许负责一些,因为NSGraphicsContext中的 -graphicsPort返回的是一个Void*类型的变量,Swift就不能推断出类型了。下面是一种解决方案:

private var currentContext : CGContext? {
    get {
        // The 10.10 SDK provides a CGContext on NSGraphicsContext, but
        // that's not available to folks running 10.9, so perform this
        // violence to get a context via a void*.
        // iOS can just use UIGraphicsGetCurrentContext.

        let unsafeContextPointer = NSGraphicsContext.currentContext()?.graphicsPort

        if let contextPointer = unsafeContextPointer {
            let opaquePointer = COpaquePointer(contextPointer)
            let context: CGContextRef = Unmanaged.fromOpaque(opaquePointer).takeUnretainedValue()
            return context
        } else {
            return nil
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值