start
开篇先简单说下,想要在blog中开主线把apple的一些自己感兴趣的guide好好学习一遍,最近在用的时候,发现绘图和动画方面确实缺了很多东西啊,但是不能像以前一样的半残的看看例子就开始用,还是想好好了解下例子。
这是cocoa drawing guide的第一篇,主要讲了cocoa drawing的基础知识和绘图上下文(Graphics Context,图形上下文)的基础知识。
文章的内容基于Cocoa Drawing Guide的内容以及自己对Cocoa Drawing Guide的一些翻译。
简介
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
类
那么包括下面的东西:
- 移动
- 缩放
- 旋转
可以用各种各样的效果组合得到有趣的结果,另外使用变形操作比你直接操作原数据要快
颜色和颜色空间
绘图之前需要指定颜色和颜色空间,NSColor
和NSColorSpace
基础的绘图元素
NSPoint
,点,有一个x,y坐标NSSize
,size包含宽,高NSRect
,矩形包含一个叫origin
的NSPoint
和一个叫size
的NSSize
,origin
是左上角的点位置,size
是宽和高
形状基本?(primitives)
可以用NSBezierPath
画一些基本形状
- 线
- 矩形
- 圆
- 曲线
- 贝塞尔曲线?
贝塞尔路径对象,保存着矢量的路径信息,保证数据小并且分辨率独立。你可以使用简单形状创建路径或是与其他基本形状组合更复杂的路径。
图片
由NSImage
提供图片,NSImageRep是图片的表示类,图片可以从文件加载或者在线(on the fly?),支持BMP,GIF,JPEG,JPEG2000,PNG,TIFF,从EPS,PDF,PICT数据得到的图片和CoreImage图片
文字
cocoa提供了先进的文件系统绘制文字,例如:
View和Drawing
cocoa中基本上所有的绘图都在view里面完成,View对象表示一个窗口上可视的一部分。一个视图用来显示一些可视的内容,也可以包括多个子视图。
NSView
是所有视图的基础,cocoa有一些基础的视图,text view,split view等,cocoa控件也是基于NSView
的。
可以基于基础的视图和控件创建自定义的视图,cocoa通过drawRect:
消息告诉你视图需要如何绘制,实现drawRect:
方法实现自定义绘图。
1 | - (void)drawRect:(NSRect)rect |
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对象的setStroke
和setFill
方法来设置描边和填充的颜色。文字的颜色不使用Fill或是Storke的,需要对文字专门进行设置。
设置路径(Path)属性
路径属性包括了很多,上面提过一些,这里说下设置路径属性有两种,一种是全局的属性通过NSBezierPath
类的类方法来设置,例如setDefaultLineWidth:
,另一种是为某个路径对象设置,使用类方法设置,例如setLineWidth:
。
设置文字属性
cocoa的字符串对象和core text系统都支持,使用属性修改字符串的表现。NSAttributedString
对象通过选择部分范围进行设置属性,NSString
直接设置整个字符串的属性。
用NSFont
对象的set font family和size方法可以设置图形状态中的全局字体设置,可以在Quartz绘制字符串的时候使用。
设置图形合成选项
嘛,这个么,用官方的图最好说明问题了
嗯,上面一堆图,基本能说明问题了设置的名字基本都是NSCompositeXXXXXXXXXX
之类的
接下来是这些模式的一个数学模型(我还是懒惰的截图了),R是结果,S是源,D是目标,Sa是源的alpha值,Da是目标的alpha值,alpha值的范围是0~1。
设置截取区域
截取区域是用来确定对view中的那一部分进行修改,调用drawRect:
之前cocoa会根据可视范围设置截取区域,可以用NSBezierPath
重新限制绘图区域。简单点的矩形可以用NSRectClip
函数创建,NSRectClipList
创建一组矩形,NSBezierPath
用来创建更复杂的图形。使用addClip
方法添加结果形状到当前的截取区域。
根据不同的环绕规则,创建出来的截取区域也将不同,如下:
下面代码创建上面图形的截取区域
1 | // If you plan to do more drawing later, it's a good idea |
可以使用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:
方法触发重绘事件,这些方法不能在非主线程中调用。
如果一定要在非主线程中更新窗口那么需要自己创建,配置那个窗口的绘图上下文。
创建位图是另一种多线程绘图的方式,因为位图的画,是自己作为容器的,所以可以安全的在非主线程中创建。
end
总算cocoa drawing guide的第一期结束了,还是写了很长时间的啊,这一期主要只是讲基础的东西和绘图上下文的一些介绍,算是入门的基础知识了,要是翻译和讲的不对的地方,还是请大家指出来啊。
最后欢迎大家订阅我的微信公众号 Little Code
- 公众号主要发一些开发相关的技术文章
- 谈谈自己对技术的理解,经验
- 也许会谈谈人生的感悟
- 本人不是很高产,但是力求保证质量和原创