Inkpad绘图原理浅析
本文新版本已转移到开源中国,欢迎前往指导。
Inkpad是一款非常优秀的iPad矢量绘图软件,保管你一看见就忘不了。我的感觉是“一览众山小”、“相见甚晚”,以至于我写的TouchVG就是“小巫见大巫”。必须好好学习这款软件的代码,破解其高性能绘图奥秘。
另外,在写这篇日志前本想使用Markdown语言写干净的博客,在 http://rhcad.github.io/ 基于Jekyll配置了日志项目,在本地配置了发布平台,无奈要做的事和要学的知识太多,半途停下来了,看来我不是当极客的料。
如果你阅读本文觉得哪里写得糟糕,可以提出来交流,如果本文能帮助你一点点就OK了,我也是在学习,本意不是想写漂亮的文章。
一、触摸交互绘图
交互式绘图当然得先看触摸响应机制,先看一张序列图:

WDCanvas是代表绘图画布的主视图,直接响应原始触摸事件(touchesBegan、touchesMoved等),没有使用双击、旋转、长按等很流行的手势识别器,奇怪吧?
我猜想Inkpad不使用手势识别器有两个原因:(1)手势识别器采用延迟识别技术进行手势二义性判断,有几百毫秒的延迟,会影响绘图快速响应的感觉;(2)Inkpad是独立的程序,所有界面都是自己的,不需要与各种带有手势识别器的界面组件共存(例如在滚动视图、电子书页面中绘图)。
在WDCanvas中通过“[[WDToolManager sharedInstance].activeTool touchesBegan:touches withEvent:event inCanvas:self]”向当前命令传递触摸事件,当然用的是单实例模式和命令模式了。
在WDCanvas的touchesBegan函数中不立即向交互命令WDTool发送触摸开始事件,而是延迟到touchesMoved才去发送。我猜想作者本想区分普通轻击(Tap)和拖动(Pan),但在实现时并不彻底:在下面的touchesEnded函数片段中,没有移动也是要发送touchesBegan的。我认为应该在touchesMoved中检查移动距离来判断是否算是已移动,而不是简单的直接切换到已移动状态。
if (!controlGesture_ && [self canSendTouchToActiveTool]) {
if (!moved_) {
[[WDToolManager sharedInstance].activeTool touchesBegan:touches withEvent:event inCanvas:self];
}
[[WDToolManager sharedInstance].activeTool touchesEnded:touches withEvent:event inCanvas:self];
}
在开始触摸、当前不是钢笔命令(WDPenTool)时,设置WDCanvas的activePath为空(self.drawingController.activePath = nil)。当前路径(activePath)用于记忆钢笔命令的当前图形路径,将activePath定义在WDDrawingController中而不是定义在WDPenTool中,是方便于在多个类中检查使用。
交互命令类的触摸响应函数使用了如下所示的模板方法模式,具体的命令类重写beginWithEvent、moveWithEvent、endWithEvent函数实现具体的绘图逻辑。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
//.....
WDEvent *genericEvent = [self genericEventForTouch:primaryTouch_ inCanvas:canvas];
[self beginWithEvent:genericEvent inCanvas:canvas];
self.previousEvent = genericEvent;
//......
} - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
//......
[self moveWithEvent:genericEvent inCanvas:canvas];
self.previousEvent = genericEvent;
//......
} - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
[self endWithEvent:genericEvent inCanvas:canvas];
} - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event inCanvas:(WDCanvas *)canvas
{
[self touchesEnded:touches withEvent:event inCanvas:canvas];
}
下面就要说说Inkpad的一大亮点:在动态拖曳绘图中使用OpenGL ES高速绘图了!
在绘图命令(例如可绘制多种图形的WDShapeTool)中,设置WDCanvas的shapeUnderConstruction属性,将当前的临时图形(WDPath)传递给WDCanvas。WDCanvas的setShapeUnderConstruction函数将调用WDSelectionView成员的立即绘制函数,WDSelectionView使用OpenGL ES 1.1绘制各种动态图形。用了OpenGL ES,拖动上千图形也回显毫无延迟感觉,这是Inkpad的亮点。
Inkpad在使用OpenGL ES 1.1绘制动态图形时,采用的优化显示方法有:(1)单点线宽,(2)忽略虚线样式,(3)不填充,(4)缓存图形轮廓路径。
触摸操作完成后就要向文档提交静态图形了:(1)绘图命令在endWithEvent中向当前层提交新的图形对象([canvas.drawing addObject:path]);(2)WDDrawingController 触发WDCanvas视图的重绘消息;(3)在WDCanvas的drawRect:函数中,遍历各个图形,调用各个对象的renderInContext函数显示所有图形,其实质是将图形的path显示到当前CGContext上。
与动态图形的绘制相比,提交静态图形并没有采用OpenGL ES,而是使用最简单的CGContext显示方式,而且是重画全部图形,没有使用CALayer等高级渲染方式。这就产生一个疑问,大量图形会不会太慢?我还没去做性能分析实验,初步估计Inkpad采用的显示优化方法是:(1)缓存CGPath轮廓路径和填充路径等对象;(2)避免几何计算和对象重构。
Inkpad已经这么好了,就需要重新评估TouchVG最近做的一些显示优化实验,看看这些计算是否有必要:(1)在GCD中异步绘制,使用前台图形列表和后台图形列表避免多线程冲突;(2)在CALayer上提前绘制,在主视图中只贴图(使用renderInContext,利用GPU贴图);(3)每一层图形一个CALayer,避免重绘所有图形;(4)是否有必要使用OpenGL ES 2.0绘制静态图形?
二、相关核心类

1、WDCanvas:绘图主视图,包含标尺视图、选择集渲染视图、删除提示线视图,包含交互命令类要用的临时图形对象。
2、WDCanvasController:绘图界面操作类,负责多种UI控件的管理和操作分发。
3、WDDrawingController:负责各种图形的增删改查逻辑。WDCanvasController是外壳功能,WDDrawingController是内核功能。
4、WDDrawing:图形文档类,容纳所有绘图内容。
5、WDLayer:一个层,包含多个图形元素WDElement。
6、WDStylable:可描边、填充的图形的基类。
7、WDAbstractPath:具有矢量路径的图形的基类。
8、WDPath:可指定线端箭头形状的矢量路径的图形类。
9、WDCompoundPath:复合路径的图形类。
10、WDTool:命令基类。
11、WDShapeTool:添加几何形状的命令,可绘制矩形、椭圆、星形、多边形、直线段、螺旋线。这些不同图形的绘图工具是在WDToolManager中构造的。
12、WDPenTool:贝塞尔曲线绘图工具。
13、WDFreehandTool:自由画光滑曲线工具。
对于色部分的Core.Model类,主要基于矢量路径设计了几何图形模型类,好像不包含图形的特征数据(即后续编辑保持形状特征)吧。
我觉得我们可以基于Inkpad对图形种类和交互命令工具进行扩充,做点行业相关的软件来,如果你感兴趣就加入讨论吧。
这两个UML图是用EA画的,InkpadUml.xmi可以导入到其他UML建模工具。
写了半天有点累了,先写到这吧。期望目标是把Inkpad吃透,结合TouchVG生出一个新孩子:)
本文新版本已转移到开源中国,欢迎前往指导。
Inkpad绘图原理浅析的更多相关文章
- HTTP长连接和短连接原理浅析
原文出自:HTTP长连接和短连接原理浅析
- Javascript自执行匿名函数(function() { })()的原理浅析
匿名函数就是没有函数名的函数.这篇文章主要介绍了Javascript自执行匿名函数(function() { })()的原理浅析的相关资料,需要的朋友可以参考下 函数是JavaScript中最灵活的一 ...
- java绘图原理------在窗口界面(或面板上)画出一张或多张图片问题解决方法
/** *@author blovedr * 功能: java绘图原理------在窗口界面(或面板上)画出一张或多张图片问题解决方法 * 日期: 2018年4月28日 16:20 * 注释: ...
- [转帖]Git数据存储的原理浅析
Git数据存储的原理浅析 https://segmentfault.com/a/1190000016320008 写作背景 进来在闲暇的时间里在看一些关系P2P网络的拓扑发现的内容,重点关注了Ma ...
- Android-Binder原理浅析
Android-Binder原理浅析 学习自 <Android开发艺术探索> 写在前头 在上一章,我们简单的了解了一下Binder并且通过 AIDL完成了一个IPC的DEMO.你可能会好奇 ...
- Dubbo学习(一) Dubbo原理浅析
一.初入Dubbo Dubbo学习文档: http://dubbo.incubator.apache.org/books/dubbo-user-book/ http://dubbo.incubator ...
- cocos2d-x游戏引擎核心之六——绘图原理和绘图技巧
一.OpenGL基础 游戏引擎是对底层绘图接口的包装,Cocos2d-x 也一样,它是对不同平台下 OpenGL 的包装.OpenGL 全称为 Open Graphics Library,是一个开放的 ...
- 沉淀,再出发:docker的原理浅析
沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...
- 阻塞和唤醒线程——LockSupport功能简介及原理浅析
目录 1.LockSupport功能简介 1.1 使用wait,notify阻塞唤醒线程 1.2 使用LockSupport阻塞唤醒线程 2. LockSupport的其他特色 2.1 可以先唤醒线程 ...
随机推荐
- android: 活动和服务进行通信
9.3.3 活动和服务进行通信 上一小节中我们学习了启动和停止服务的方法,不知道你有没有发现,虽然服务是在活 动里启动的,但在启动了服务之后,活动与服务基本就没有什么关系了.确实如此,我们在 活 ...
- WHY翻写NEHE与红龙的3D图形程序 [开源]
个人认为学习3D图形程序开发,有两套经典教程.D3D的<D3D游戏开发编程基础>也就是红龙那本书.OpenGL的NEHE教程.为向经典致敬,将我之前翻写他们的程序开源. 我所做的工作是将其 ...
- Hadoop学习篇 2 初识 Hadoop
在一个全配置的集群上,运行Hadoop意味着在网络分布的不同服务器上运行一组守护进程 (daemons),这些守护进程或运行在单个服务器上,或运行与多个服务器上,他们包括: (1) NameNode( ...
- AYUI快速开发2016-6-29 ,全部免费,WPF普遍之路梦想开启
下载开发模板:下载 AYUI 6月29日起,免费使用,无需授权,去除所有限制,关爱开发者,不求捐赠,只要你们能私活挣到钱就行,你们没有欠我的.我希望所有人都能开发WPF的东西 使用教程,上面的下载文件 ...
- Java 模板引擎 jetbrick-template
jetbrick-template 是一个新一代 Java 模板引擎,具有高性能和高扩展性. 适合于动态 HTML 页面输出或者代码生成,可替代 JSP 页面或者 Velocity 等模板. 指令和 ...
- SSIS连接Oracle遇到的问题
Fuck!一大早上来到办公室发现 E盘被客户无缘无故干掉了,心中一万只......路过,but 接下来还是要解决问题 冷静!冷静!冷静! 问题还是要解决的 于是乎去测试开发环境 发现DW库和Repo ...
- activemq安装与简单消息发送接收实例
安装环境:Activemq5.11.1, jdk1.7(activemq5.11.1版本需要jdk升级到1.7),虚拟机: 192.168.147.131 [root@localhost softwa ...
- asp.net mvc 动态显示不同的部分视图
首先是AJAX请求 //第一次打开 默认s单行文本 $.ajax({ type: "GET", url: "/Admin/Field/ChoiceType4Edit&qu ...
- Android开发(二十五)——Android上传文件至七牛
设置头像: Drawable drawable = new BitmapDrawable(dBitmap); //Drawable drawable = Drawable.createFromPath ...
- javascript 搜索二叉树
function Tree() { this.root = null; } Tree.prototype = { constructor: Tree, addItem: function(value) ...