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

所有文章已搬迁到个人站点:me.harley-xk.studio,欢迎访问留言

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

MaLiang 现已全面升级到 Metal。
本文已针对最新 Metal 版本更新,最后更新于:2019-05-10

这是什么?

MaLiang 是 iOS 平台基于 Metal 的涂鸦绘图框架,完全使用 Swift 实现,支持自定义纹理、缩放、压力感应等特性,并且提供了较大的自定义扩展的空间。

特色功能

  • 笔迹通过贝塞尔曲线进行平滑处理
  • 支持添加自定义贴图
  • 纹理和贴图支持旋转
  • 内置一个荧光笔特效的实现
  • 可以根据压力自动调整笔迹,支持 3D Touch
  • 支持撤销和重做
  • 支持滚动和缩放画布
  • 导出绘制内容为图片
  • 将绘制内容保存为矢量文档和从文档恢复数据

使用

我的理念是尽量制造简单、优雅的东西,虽然有时候要做到这一点其实很难,但是尽量往这方面靠吧。MaLiang 的集成和使用都很简单,我把大量对使用者来说没有什么用也没有必要了解的内部逻辑都隐藏了。这篇文章也会介绍一些内部实现的思路。

集成

Cocoapods

MaLiang 已经推送到了 Cocopods 的官方 repo,所以,你只需要在 Podfile 增加一条 Pod 指令然后 install 就可以在项目中使用了:

pod 'MaLiang'

如果你想使用老的 OpenGL 版本,请指定到 1.1.x 版本

pod 'MaLiang', '~> 1.1'

需要注意 OpenGL 版本只包含有限的功能并且已停止维护

Carthage

MaLiang 现在也支持通过 Carthage 集成,首先在 Cartfile 中增加指令:

github "Harley-xk/MaLiang"

然后执行 cartage update 命令来完成编译,然后将编译完成的 MaLiang.framework 添加到 Xcode 项目中。

需要确保将 MaLiang.framework 添加到 target 的 Embedded Binaries

调用

在需要使用的地方引入 Mudule。当然,首先需要编译一下,否在会报找不到 Moudle 的错误

import MaLiang

几个主要的类

1. Canvas

画布是 MaLiang 最基础的组件,所有的涂鸦都发生在 Canvas 上。Canvas 本质上是一个 UIView,所以你可以使用任何你原来创建 UIView 的方法来创建一个画布,并将它添加到你的界面上。

  • 如果你偏好代码流,那么直接调用 UIView 的通用构造函数 init(frame:) 就可以了。

  • 如果你觉得 IB 流才是正道,只要在 xib 或者 storyboard 中拖一个 UIView 到界面上,然后将类名改成 Canvas 后回车就可以了,Xcode 应该会自动将 Module 设置成 MaLiang

Canvas 设置正确的布局约束,然后你就可以开始涂鸦了,比如写两个毛笔字:

嗯,请忽略我的书法水平... 想画成这样,还缺少一些东西 :)

Canvas 继承自 MetalView, MetalView 做了几乎所有与 Metal 打交道的事情,虽然它被定义成一个 open 的类,但是如果不是深度定制的话,应该不需要对它进行扩展。

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

MetalView 初始化之后会创建一个默认的纹理,这个纹理就是一个简单的不透明的圆点,所以只能画最简单的线条。如果想要画出上图那样的效果,就需要使用相对复杂一点的纹理了。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 设备的 3D Touch 参数,另外在一些不支持压力感应的设备上使用模拟的压力感应。模拟压感依赖手势移动的速度来判断压力的大小。

  • color 颜色

影响笔迹的颜色,实际画出的颜色会计算进 opacity 的值,不过由于纹理之间会叠加,所以相互效果可以基本抵消。你一般不需要为颜色额外指定透明度的值。

  • texture 纹理

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

对于纯色线条,颜色是根据对画笔所指定的 color 属性来决定的,纹理只需要提供准确的透明度信息即可,因此可以是任意颜色。

3. CanvasData

CanvasData 会在画布创建后自动初始化,它保存了当前画布上的所有笔迹信息,依赖这些数据,目前实现了撤销和重做功能。
通过 CanvasData 持有的数据,还可以轻松实现保存涂鸦数据到文件的逻辑。反过来也可以将保存的数据重新还原成画布图像,这样可以实现跨设备的数据同步功能。MaLiang 提供了一个默认版本的数据保存和读取的逻辑,你可以在此基础上做进一步的封装,也可以完全另外实现你自己的存储逻辑。

MaLiang 进化史

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

  • 18年业余时间重拾了这个项目,基于 OpenGL ES3,使用 Swift 完全重写,同时对整个项目结构进行了重新设计和改进,并进行了少量的扩展。

  • 然而生不逢时,MaLiang 刚完成没多久,苹果就宣布废除 OpenGL ES,iOS 平台 Metal 成了唯一选项。但由于我对 OpenGL ES 都只是一知半解,Metal 就研究的更少了,将 MaLiang 迁移到 Metal 的计划便一直处于搁浅状态。

  • 直到最近才终于下定决心,从渲染管线到着色器语言,恶补了 Metal 相关的基础知识,终于将底层绘制相关的逻辑全部用 Metal 改写,并且对上层逻辑进行了部分重构。同时顺便将原来基本没有用户体验的缩放功能完全重写,实现了可以放大画布之后绘制微缩图案的功能。

当然了,我对 Metal 的理解还很浅薄,MaLiang* 还是有很多需要优化改进的地方。*

Why Swift?

Swift 从 15 年开始就作为我的主要开发语言了,所以回去写 OC 对我来说已经不可能,尽管之前写 OpenGL 时使用 Swift 确实很麻烦,不过最后还是完成了。

当时有大佬奉劝我使用 OC 或者 C 作为中间层来调用 OpenGL,再用 Swift 封装上层逻辑,确实这样可以以最低的成本实现需要的效果。不过作为一个业余项目,成本并不是我第一考虑的要素,而且这个库虽然是基于 OpenGL 的,但是真正跟 OpenGL 打交道的,其实也就那几百行代码。为了追求这一点点成本和便利性,牺牲整个项目结构的统一和整洁,在我这是无法接受的。

而坚持使用 Swift 的好处在最近迁移到 Metal 时就表现出来了。只需要对几处OpenGL的关键绘制逻辑使用 Metal 改写,就能很快完成迁移,否则就需要重写整个底层逻辑了 :)

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

在这个时间点,Swift 5 刚好发布没多久,终于迎来了 ABI 稳定这一天,这意味着 Swift App 距离脱离 OC 的动态运行时,完全运行在纯 Swift 环境中那一天已经不远了:)

应用

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

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

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

基于 MaLiang 的涂鸦 App 已经开发完成并上线,可以在 AppStore 搜索 马良 下载体验

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 如果你有一只笔可以点亮整座微型城市,是不是很炫? 视频中,看到的的笔就是日本电子公司Kandenko研发了一支电路...
    隐仕传媒阅读 436评论 0 0
  • 各位家长好久不见啦!当迎走了我们欢乐的“双节”又迎来了我们高级班的3D打印课啦! 前面四节课都让孩子们...
    慧诺教育罗老师阅读 445评论 0 0
  • 从前,有个孩子名字叫马良。他的父亲母亲死的早,他就靠自己打柴、割草过日子。他从小喜欢画画,可是,他连一支笔也没有。...
    XM小魔阅读 4,921评论 0 1
  • 卡尔的《积极心理学》在谈“积极特质与动机”的时候说强调“遗传因素影响人格特质的机制是复杂的。或许,是多种基因共同决...
    凌宗伟阅读 579评论 0 2
  • 这不是一篇讲实用性知识的文章,相反本人觉得反而它甚至有点消极,所以请观看者慎重。 近段时间以来,甚至一两个月也...
    学习飞翔的小飞鸟阅读 335评论 0 0