Cocoa Drawing Guide学习part1——基础和图形上下文 (转)

原文:http://noark9.github.io/2013/12/28/cocoa-drawing-guide-study-part-1/

简介

cocoa drawing由AppKit提供并且也兼容其他的模式:
Quartz,OpenGL,Core Image,Core Video,Quartz Composer,PDF Kit,QuickTime

基于Quartz,所以AppKit提供了Quartz相关的功能

  • 基于path的绘图
  • 创建,加载,显示图片
  • 布局和显示文本
  • 创建,显示PDF
  • 半透明
  • 阴影
  • 色彩管理
  • 变形
  • 打印
  • 抗锯齿渲染
  • OpenGL

cocoa drawin基于Quartz可以利用硬件资源进行渲染,并且使用的是打印机的模式。因此不同的绘图顺序也会得到不同的结果。

绘图环境

绘图环境决定了绘图最终的结果,canvas决定了绘图内容在哪里,绘图设置控制绘图的大小,颜色,质量,方向等。

Graphics Context - 绘图上下文

绘图上下文可以说是绘制的位置,封装了绘图所需要的各种信息。
那么绘图的目标可以是下面的东西

  • Window(或者View)
  • 图片,包括了所有类型的Bitmap图片
  • 打印机
  • PDF,EPS文件
  • OpenGL界面

NSGraphicsContext也管理了绘图目标的状态,这些状态会影响如何绘图,比如闲的宽度,颜色,填充色,绘图状态可以保存在当前图形上下文的栈上,所有的改变都可以通过回滚绘图状态来撤销。
cocoa管理的一些属性和Quartz不同,比如颜色使用NSColor,大部分的基于路径的绘图使用NSBezierPath

Coordinate System - 协调系统

Coordinate System是cocoa支持使用Quartz应用的,使用浮点数,绘图代码绘制在用户的协调空间,在渲染到具体设备的时候,转换到设备的Coordinate Space。Coordinate System

User Coordinate Space使用具体的值,每一个单位是1/72英寸,但并不代表是72dpi。不使用像素或是dpi,你只需要考虑大小,而显示由cocoa去关心。Device Coordinate Space使用设备的解析度单位,cocoa负责Device Coordinate Space和User Coordinate Space的转换。

变形

变形操作2d协调空间,变形使用一个变形的数学矩阵,获取变形属性如何修改协调,cocoa中变形是NSAffineTransform
那么包括下面的东西:

  • 移动
  • 缩放
  • 旋转

可以用各种各样的效果组合得到有趣的结果,另外使用变形操作比你直接操作原数据要快

颜色和颜色空间

绘图之前需要指定颜色和颜色空间,NSColorNSColorSpace

基础的绘图元素

NSPoint,点,有一个x,y坐标
NSSize,size包含宽,高
NSRect,矩形包含一个叫originNSPoint和一个叫sizeNSSizeorigin是左上角的点位置,size是宽和高

形状基本?(primitives)

可以用NSBezierPath画一些基本形状

  • 线
  • 矩形
  • 曲线
  • 贝塞尔曲线?

primitivesprimitives

贝塞尔路径对象,保存着矢量的路径信息,保证数据小并且分辨率独立。你可以使用简单形状创建路径或是与其他基本形状组合更复杂的路径。

图片

NSImage提供图片,NSImageRep是图片的表示类,图片可以从文件加载或者在线(on the fly?),支持BMP,GIF,JPEG,JPEG2000,PNG,TIFF,从EPS,PDF,PICT数据得到的图片和CoreImage图片

文字

cocoa提供了先进的文件系统绘制文字,例如:

texttext

View和Drawing

cocoa中基本上所有的绘图都在view里面完成,View对象表示一个窗口上可视的一部分。一个视图用来显示一些可视的内容,也可以包括多个子视图。

NSView是所有视图的基础,cocoa有一些基础的视图,text view,split view等,cocoa控件也是基于NSView的。

可以基于基础的视图和控件创建自定义的视图,cocoa通过drawRect:消息告诉你视图需要如何绘制,实现drawRect:方法实现自定义绘图。

1
2
3
4
5
 
- (void)drawRect:(NSRect)rect
{
    // Custome draw code
}

drawRect:调用时,cocoa将绘画焦点锁定到你的view,保存绘图转贴,调整当前的变形矩阵适应你的视图的方向,调整从view中截取的矩形,我们只需要完成绘制就可以了。

一些常用的绘图任务

打印和创建PDF,EPS的话,在这里就不看了,实际用到的时候看吧。

绘制自定义视图

在自定义视图中实现drawRect:方法可以使用路径,文字,图形或者Cocoa,Quartz,OpenGL等东西

响应内容变化,更新视图

向View发送setNeedsDisplayInRect:或者setNeedsDisplay:消息,告诉View部分或者全部的内容已经失效并且需要更新,在下一个更新循环的时候cocoa会响应后发送一个drawRect:消息给View进行更新。

让一些东西发生动画

使用Core Animation,设置Timer,或者使用NSAnimation或者NSViewAnimation类时在一定帧率产生的通知。接收到消息时,让view中部分或者全部失效来进行强制更新。可以参考Core Animation Programming Guide,以及后面会说道的NSTimer和使用Core Animation Objects。

改变大小

NSView中的inLiveResize方法判断是否正在发生改变大小的事件。为了保证你的View能够如预期一样,那么尽量少的进行绘制,可以参考Drawing Performance Guidelines。

Graphics Context——图形上下文

标识当前绘图的上下文是设备,屏幕,还是文件,Coordinate System,边界等一些图形属性。基本上不需要人工创建一个图形上下文。Cocoa应用中基本上所有的画布都使用NSGraphicsContext。(OpenGL的话,使用NSOpenGLContext)

基础知识

drawRect:调用的之前,cocoa就会对当前的绘图上下文先有一定的处理:

1 保存当前图形状态,保证可以undo
1 添加一个适当的变形,保证方向跟现在View的方向是一致的
1 在可视范围内截取区域,防止内容被其他的视图(Straying into other views)渲染

你的绘图将会发送到Quartz Compositor和其他在窗口中的视图合并起来。

在你的drawRect:结束后,cocoa回复绘图上下文为下一个view绘制做准备。

当前的图形上下文

可以这样获取

NSGraphicsContext *context = [NSGraphicsContext currentContext];

使用saveGraphicsState保存当前的图形状态
使用restoreGraphicsState弹出当前的状态并恢复到上一个保存的状态
这两个方法,调用需要成对

这两个方法的类方法,作用于当前的图形上下文
这两个方法的实例方法,作用于特定的图形上下文

图形状态信息

  • Current transformation matrix(CTM)

映射view的Coordinate System和目标设备的Coordinate System用的。cocoa在调用drawRect:之前会修改CTM,可以使用一个NSAffineTransform对象修改CTM的朝向,缩放,旋转当前的Coordinate System。

  • Clipping areas

截取区域描述了用于调用绘制函数的画布区域,cocoa在调用drawRect:之前会修改截取区域为可视区域,可以使用NSBezierPath对象来进一步设置可视区域。

  • Line width

设置路径的宽度,默认是1.0,可以使用NSBezierPath对象修改这个值。

  • Line join style

线合并描述了线是如何合并的,默认是NSMiterLineJoinStyle,可以使用NSBezierPath对象修改这个值。

  • Line cap style

描述一个路径的开闭,默认是NSButtLineCapStyle,可以使用NSBezierPath对象修改这个值。

  • Line dash style

描述线的断开模式,也包括开始的模式,这个属性没有默认值,表示是实线,可以修改NSBezierPath对象的这个样式。

  • Line miter limit

定义线在什么时候合并成一个。只有Line join style使用了NSMiterLineJoinStyle的时候起效。miter的长度已经被线的宽度除后,如果超过了miter limit的话,那么就用斜面,默认是10.0,可以使用NSBezierPath对象修改这个值。

  • Flatness value

描述了实际上是那一部分曲线被渲染了。数值越小,那么曲线越平滑,也会有跟复杂的运算,这个值在不同的设备渲染时影响非常小。默认值是0.6,可以使用NSBezierPath对象修改这个值。

  • Stroke color

使用NSColor设置,渲染路径的颜色。

  • Fill color

使用NSColor设置,渲染路径所包含区域的颜色(填充色)。

  • Shadow

使用NSShadow描述所渲染内容的阴影。

  • Rendering intent

描述颜色映射,cocoa不支持,需要使用Quartz。

  • Font name,Font Size

使用NSFont设置

  • Font character spacing

描述文字的字符空间。

  • Text drawing mode

描述文字如何进行渲染(这个属性并不是由Cocoa直接支持的)

  • Image interpolation quality

描述渲染时图形插值处理,使用NSGraphicsContext类进行设置。

  • Compositing operation

描述合成过程(cocoa中基于Quartz blend模式来进行支持,但是使用了不要同的使用方法和行为),使用NSGraphicsContext类进行设置,一些渲染方法能够提供额外的设置。

  • Global alpha

设置一个全局的alpha值,用于叠加在使用的颜色的alpha值上。cocoa不直接支持这个属性,通过Quartz中的CGContextSetAlpha函数设置。

  • Anti-aliasing setting

设置抗锯齿,使用NSGraphicsContext设置。

屏幕画布和打印画布

打印本来是不考虑了,不过既然这里单独提到了一节,那我也就顺便一起看下了。

一般啊,cocoa的绘图上下文,有两种画布,屏幕上的和打印的。在屏幕上的cocoa提供一个绘图上下文作给有view更新绘制或者响应用户打印文档时使用。但是下面几种情况是需要手工创建图形上下文的:

  • 使用OpenGL渲染View的内容
  • 绘制屏幕外的位图
  • 创建PDF和EPS
  • 通过程序开始一个打印job

使用NSGraphicContext的类方法,可以创建使用屏幕作为画布的图形上下文对象,这些方法无法创建打印的画布,cocoa把所有的打印都通过cocoa printing system(cocoa 打印系统)来处理这些任务并为你提供绘图上下文。因此,我们需要使用NSPrintOperation创建打印任务,并且你的view至少要提供最少的打印支持。

可以用示例方法isDrawingToScreen或者类方法currentContextDrawingToScreen判断画布的类型。打印类型的画布可以用attributes方法获取画布的更多信息。

绘图上下文和Quartz

NSGraphicsContext在cocoa里面是Quartz绘图上下文CGContextRef数据的一个转化。转化和使用很容易。

修改当前的绘图上下文状态

drawRect:方法中可以用NSGraphicsContext的方法直接修改大部分绘图状态属性,但是还有一些属性需要借助其他的一些对象。

保存和回复当前绘图状态是消耗非常大的动作,尽量在要马上撤销一些操作的时候,或者没有修改的时候用,比如重置截取区域的时候。

设置颜色和模式

cocoa提供了很多的颜色空间,NSColor默认支持RGB,CMYK,灰度,也可以用ICC和ColorSync描述来支持自定义颜色空间。使用NSColor对象的setStrokesetFill方法来设置描边和填充的颜色。文字的颜色不使用Fill或是Storke的,需要对文字专门进行设置。

设置路径(Path)属性

路径属性包括了很多,上面提过一些,这里说下设置路径属性有两种,一种是全局的属性通过NSBezierPath类的类方法来设置,例如setDefaultLineWidth:,另一种是为某个路径对象设置,使用类方法设置,例如setLineWidth:

设置文字属性

cocoa的字符串对象和core text系统都支持,使用属性修改字符串的表现。NSAttributedString对象通过选择部分范围进行设置属性,NSString直接设置整个字符串的属性。

NSFont对象的set font family和size方法可以设置图形状态中的全局字体设置,可以在Quartz绘制字符串的时候使用。

设置图形合成选项

嘛,这个么,用官方的图最好说明问题了

Compositing operations in CocoaCompositing operations in Cocoa

嗯,上面一堆图,基本能说明问题了设置的名字基本都是NSCompositeXXXXXXXXXX之类的

接下来是这些模式的一个数学模型(我还是懒惰的截图了),R是结果,S是源,D是目标,Sa是源的alpha值,Da是目标的alpha值,alpha值的范围是0~1。

Mathematical equations for compositing colorsMathematical equations for compositing colors

设置截取区域

截取区域是用来确定对view中的那一部分进行修改,调用drawRect:之前cocoa会根据可视范围设置截取区域,可以用NSBezierPath重新限制绘图区域。简单点的矩形可以用NSRectClip函数创建,NSRectClipList创建一组矩形,NSBezierPath用来创建更复杂的图形。使用addClip方法添加结果形状到当前的截取区域。

根据不同的环绕规则,创建出来的截取区域也将不同,如下:

Clipping paths and winding rulesClipping paths and winding rules

下面代码创建上面图形的截取区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// If you plan to do more drawing later, it's a good idea
// to save the graphics state before clipping.
[NSGraphicsContext saveGraphicsState];
 
// Create the path and add the shapes
NSBezierPath* clipPath = [NSBezierPath bezierPath];
[clipPath appendBezierPathWithRect:NSMakeRect(0.0, 0.0, 100.0, 100.0)];
[clipPath appendBezierPathWithOvalInRect:NSMakeRect(50.0, 50.0, 100.0,  100.0)];
 
// Add the path to the clip shape.
[clipPath addClip];
 
// Draw the image.
 
[NSGraphicsContext restoreGraphicsState];

可以使用setClip方法设置截取区域,但是这个方法将会替换原来的截取区域,

设置抗锯齿选项

这个效果么,大家应该都知道了,可以用NSGraphicContext中的setShouldAntialias:方法设置。

创建绘图上下文

如果在view和打印的画布,可以用cocoa提供的绘图上下文。不过如果你要做其他类型的绘图,那么就需要自己创建了。

创建一个基于屏幕的绘图上下文

如果需要在常规的更新周期之外绘制图形,那么需要单独的创建一个图形上下文。这样可以在另外一个线程中,在一个屏幕外的窗口或是位图上进行绘图,之后再拷贝结果,使用NSGraphicsContext类中的方法可以为特定的窗口或是位图创建绘图上下文。

在window上绘图

使用graphicsContextWithWindow:方法为一个特定的窗口创建绘图上下文,当时如果那个窗口有很多子视图,那就很麻烦了,你需要一个一个的去设置,这个建议使用在创建屏幕外的缓冲区的时候用。

这里说下,OSX的大部分窗口已经使用了双缓冲机制,不需要淡腾的再去用屏幕外的窗口和位图去刷新ui,费时费力。

在位图上绘图

在10.4以前, 需要通过截取view或是window,来实现位图绘制。10.4以后可以通过graphicsContextWithBitmapImageRep:方法为一个NSBitmapImageRep对象设置绘图上下文,绘图将会直接渲染到位图上。

创建一个PDF或是PostScript的绘图上下文

不同与之前的那个PDF和PostScript不能那么直接的创建,他们都要通过cocoa打印系统。
最快捷的方法是用NSView对象的dataWithPDFInsideRect:dataWithEPSInsideRect:方法自动的用view中的绘图代码创建打印任务,输出PDF和EPS。可以使用NSPrintOperation类手工创建打印任务,用runOperation方法开始打印队列。

打印过程中,cocoa调用view上的一些方法来处理布局和绘制,实现这些方法可以支持PDF和EPS的打印。

线程和绘图上下文

AppKit为所有窗口和线程的合并管理一个绘图上下文。对应一个窗口,每个线程都有一个绘图上下文,也可以使用另一个线程来进行绘图,但是这样会有一些警告。

正常的窗口更新循环中,所以的绘图请求都会发送到应用程序的主线程中。可以通过调用setNeedsDisplay:或者setNeedsDisplayInRect:方法触发重绘事件,这些方法不能在非主线程中调用。

如果一定要在非主线程中更新窗口那么需要自己创建,配置那个窗口的绘图上下文。

创建位图是另一种多线程绘图的方式,因为位图的画,是自己作为容器的,所以可以安全的在非主线程中创建。

posted @ 2013-12-29 00:50  yingkong1987  阅读(1376)  评论(0编辑  收藏  举报