神笔马良——基于 OpenGL 的涂鸦框架

取这个名字有投机取巧的嫌疑,但愿能对得起先贤 >_<git

这是什么?

MaLiang 是 iOS 平台一个基于 OpenGL ES3 的涂鸦绘图库,使用纯 Swift 实现,支持自定义纹理、压力感应、自动笔触等特性,而且提供了必定的自定义扩展的空间。github

这篇文章能够看做是对 Github 上 README 说明的详细扩展和补充说明。canvas

使用

个人理念是尽可能制造简单、优雅的东西,虽然有时候要作到这一点其实很难,可是尽可能往这方面靠吧。MaLiang 的集成和使用都很简单,我把大量对使用者来讲没有什么用也没有必要了解的内部逻辑都隐藏了。固然了,若是你的好奇心很重,能够本身去看源代码。这篇文章也会介绍一些内部实现的思路。swift

集成

MaLiang 已经推送到了 Cocopods 的官方 repo,因此,你只须要在 Podfile 增长一条 Pod 指令而后 install 就能够在项目中使用了:ruby

pod 'MaLiang'
复制代码

而后在须要使用的地方引入 Mudule。固然,首先须要编译一下,否在会报找不到 Moudle 的错误框架

import MaLiang
复制代码

几个主要的类

1. Canvas

画布是 MaLiang 最基础的组件,全部的涂鸦都发生在 Canvas 上。Canvas 本质上是一个 UIView,因此你可使用任何你原来建立 UIView 的方法来建立一个画布,并将它添加到你的界面上。机器学习

  • 若是你偏好代码流,那么直接调用 UIView 的通用构造函数 init(frame:) 就能够了。函数

  • 若是你以为 IB 流才是正道,只要在 xib 或者 storyboard 中拖一个 UIView 到界面上,而后将类名改为 Canvas 后回车就能够了,Xcode 应该会自动将 Module 设置成 MaLiang布局

Canvas 设置正确的布局约束,而后你就能够开始涂鸦了,好比写一个像下面这样的东东 :)学习

嗯,想画成这样,确实还缺乏一些东西 :)

Canvas 继承自 MLView(ML是 MaLiang 的缩写,不是那个机器学习的东东),MLView 作了几乎全部与 OpenGL 打交道的事情,虽然它被定义成一个 open 的类,但实际使用中基本是用不到的。不过了解一些原理也无伤大雅么~

OpenGL 涂鸦的核心是纹理(Texture),本质上就是沿着手指轨迹,不断地将纹理叠加到画布上的过程。因此能画出什么样的笔迹,彻底取决于使用的纹理,以及它的大小、颜色、尺寸等参数。

MLView 初始化以后会使用自带的图片建立一个默认的纹理,这个纹理就是一个简单的不透明的圆点,因此只能画最简单的线条。若是想要画出上图那样的效果,就须要使用相对复杂一点的纹理了。MaLiang 的示例项目里面提供了好几个设定好的纹理,用他们能够模拟出铅笔、水笔以及毛笔的特效,上面的文字就是使用毛笔特效写出来的。

快照

Canvas 提供了一个简单的快照功能:

open func snapshot() -> UIImage?
复制代码

调用该方法会对画布生成一个当前内容的快照并以 Image 的形式返回,快照的实现逻辑很简单,你也能够本身实现更加复杂的快照逻辑。

2. Brush

直接使用纹理仍是比较繁琐的,另外与纹理相关的还有颜色、线条的粗细以及其余一些参数,因此这里提供了一个 Brush 类来处理全部的这些数据。

Brush 的属性在改变后会马上影响接下来的绘制效果。

  • opacity 透明度

上面提到,涂鸦的本质是把纹理叠加到画笔的过程,因此想要作出深浅不一的笔迹,纹理就须要具备透明度,能够经过opacity 属性来调节。

  • pointSize 笔迹粗细

pointSize 直接影响笔迹的粗细,它是以 iOS 尺寸的标准单位 点(point) 来衡量的,因此这是一个自适应屏幕像素密度的属性。你不须要根据设备类型来计算实际像素,直接指定眼睛可见的大小就能够了。

  • pointStep 点距

同上,因为笔迹是经过叠加纹理实现的,所以除了透明度外,两个纹理之间的距离也会影响到笔迹的深浅。另外若是把点距设定到大于笔迹的尺寸,甚至能够画出相似虚线的效果。点距的单位也是 点(point)

  • forceSensitive 压力敏感度

之因此说 pointSize 是影响笔迹的粗细,而不是直接肯定,是由于有压力感应的存在。笔迹的实际尺寸会随着压力的大小在 pointSize 指定的尺寸上下浮动,压力越大,笔迹越粗。forceSensitive 影响笔迹对压力浮动的剧烈程度,建议设置为 0 - 1 之间的某个值。若是设置过大,笔迹随压力的便会会太过剧烈而失真;若是将 forceSensitive 的值设置为 0,则对该画笔关闭压力感应效果,笔迹粗细不会随着压力而变化。

MaLiang 默认使用 iOS 设备的压力感应特性,另外在一些不支持压力感应的设备上使用模拟的压力感应。模拟压感依赖手势移动的速度来判断压力的大小,速度越快压力越小。

  • color 颜色

影响笔迹的颜色,实际画出的颜色会计算进 opacity 的值,不过因为纹理之间会叠加,因此相互效果能够基本抵消。你通常不须要为颜色额外指定透明度的值。

  • texture 纹理

texture 是一个非公开属性,实际使用时只须要使用纹理图的 Image 初始化 Brush 对象就能够了,不须要关心 texture 的具体实现。

实际绘制时的颜色是设定的 color 与纹理的颜色混合以后的结果,因此须要保证纹理图是白色的,才能确保绘制正确的颜色。这个问题可能会在将来改善。

texture 其实是一个 MLTexture 类型的对象,MLTexture 内部分装了纹理相关的 OpenGL 实现,包括建立纹理、切换画笔时的纹理绑定等。

3. Document

Document 不是实现涂鸦的必备组件,它是为了提供一些更加深刻的功能而设计的。Document 维护着持有它的画布的全部笔迹数据,依赖这些数据,能够实现撤销和重作功能。这两个功能 MaLiang 已经默认实现。

经过 Document 持有的数据,你还能够轻松实现保存涂鸦数据到文件的逻辑。反过来也能够将保存的数据从新还原成画布图像,这样能够实现跨设备的数据同步功能。

Document 功能默认是没有启用的,须要手动经过代码开启:

canvas.setupDocument()
复制代码

Document 在运行过程当中须要使用一部分硬盘空间来存放临时数据,因此若是设备存储空间不足时,上面的操做会抛出一个异常,为了保证程序的健壮性,建议使用 do-catch 模式来捕获可能的异常状况:

do {
    try canvas.setupDocument()
} catch {
    // do somthing when error occurs
}
复制代码

计划实现的一些特性

计划中 MaLiang 还存在一些还没有实现的特性,这些特性会在将来逐渐添加进来,固然,你也帮助我实现,而后给我提交 PR :)

  • [x] 撤销 & 重作,目前已经实现

  • [x] 导出图片,已实现

  • [ ] 绘制文本到画布中的指定位置

  • [ ] 绘制指定的图片到画布中的指定位置

  • [ ] 纹理旋转,旋转纹理能够实现一些更加特殊的笔迹效果

由来

MaLiang 起源于多年前的一个涂鸦项目,当时仍是基于 Objective-C 和 OpenGL ES1 实现的,OpenGL ES1 对于抗锯齿的支持不是很好,因此涂鸦的效果不怎么敢恭维。而且当时因为太年轻,整个框架的设计和结构都比较凌乱。虽然最后顺利上架了一段时间,不过因为各类各样的缘由,整个项目随当时的公司一块儿无疾而终了。

去年开始重拾这个项目,打算基于 Swift 和 OpenGL ES3 彻底重写,同时将当时处理得不是很好的地方加以改进,另外扩展了一些本身近期想到的东西,最终诞生了这个库。

Why Swift?

使用 Swift 直接和 OpenGL 打交道确实不是一件容易的事情,有人奉劝我使用 OC 或者 C 做为中间层来调用 OpenGL,再用 Swift 封装上层逻辑,确实这样能够以最低的成本实现须要的效果。

不过做为一个业余项目,成本并非我第一考虑的要素,并且这个库虽然是基于 OpenGL 的,可是真正跟 OpenGL 打交道的,其实也就那几百行代码。为了追求这一点点成本和便利性,牺牲整个项目结构的统一和整洁,在我这是没法接受的。

另外,引入 OC 代码意味着同时引入了 OC 的动态运行时环境,这对 Swift 的执行效率会有必定的影响。虽然做为一个 iOS 的项目,如今必然没法摆脱 OC 的动态运行时环境,个人这点偏执彷佛也没有什么意义,不过谁知道之后会怎么样呢 :)

应用

说了半天,这个库有什么用?说实话我也不知道,或许能够用来作签名?不过签名其实用 CoreGraphics 就足够了。或许能够用它来作一个画画的 App 来逗小孩玩,可能我真会这么干。。。

说到底,这主要是对当初懵懂时期经历的一个记念吧。感兴趣的均可以拿去玩 :)

接下来可能会打算基于这个库开发一款涂鸦的 App。固然了,多年前的那个项目是不会复活了,新的这个 App 会是一个融合了不少我本身想法的全新项目。固然了但愿不要半途而废 - -!

相关文章
相关标签/搜索