ForceSketch:使用 CIImageAccumulator 的 3D Touch 绘图应用程

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

继我最近关于 3D Touch 触摸合并的 帖子之后,将两者结合在一个简单的绘图应用程序中似乎是显而易见的下一步。这也让我有机会修改 iOS 9 中新引入的 CIImageAccumulator

我的小型演示应用程序 ForceSketch 允许用户在他们的 iPhone 6 屏幕上绘图。线条粗细和线条颜色都与触摸压力相关联。就像我的 ChromaTouch 演示一样,压力控制色调,所以最轻的触摸是红色,在最大压力的三分之一时变成绿色,在三分之二时变成蓝色,在最大压力时变回红色。

一旦用户抬起手指,两个 Core Image 过滤器 CIColorControls 和 CIGaussianBlur 就会启动并使绘图淡出。

ForceSketch 的绘图机制

绘图代码都是从我的 视图控制器的 touchesMoved 方法调用的。在这里,我根据合并的触摸创建了一个 UIImage 实例,并将该图像合成到现有图像累加器上。在生产应用程序中,我可能会在后台线程中执行图像过滤以提高用户界面的性能,但是对于这个演示,我认为这种方法是可行的。

开头的 guard 语句确保我对最重要的项目有非可选常量:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


下一步是准备创建图像对象。为此,我需要开始图像上下文并创建对当前上下文的引用:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


为确保获得用户手势的最大保真度,我遍历合并的触摸 - 这为我提供了在调用 touchesMoved() 之间可能发生的所有中间触摸。


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


使用每次触摸的 force 属性,我为线段颜色和粗细创建常量。为了确保使用非 3D Touch 设备的用户仍然使用该应用程序,我检查了 forceTouchCapability 并为这些用户提供了固定的权重和颜色:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


使用这些常量,我可以在图形上下文中设置线宽和描边颜色:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


...我现在准备好为这个合并的触摸定义我的线段的开始和结束:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


合并触摸循环中的最后一步是描边路径并更新 previousTouchLocation


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


一旦所有的笔画都被添加到图形上下文中,只需一行代码就可以创建一个 UIImage 实例,然后结束上下文:



 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


显示绘制的线条

为了显示 drawnImage 中保存的新绘制的线条,我使用了一个 CISourceOverCompositing 过滤器,其中 drawnImage 作为前景图像,图像累加器的当前图像作为背景:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


然后通过合成器获取源的输出,将其传回累加器并使用累加器的图像填充我的 UIImageView


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


模糊淡出

一旦用户抬起手指,我就会对绘制的图像进行“模糊淡出”。此效果使用两个定义为常量的 Core Image 过滤器:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


效果的第一部分是使用 CADisplayLink ,它将在每次屏幕刷新时调用 step()


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


我依靠 previousTouchLocation 为 nil 来推断用户已完成触摸。如果是这种情况,我只需将累加器的当前图像传递到 HSB/颜色控制滤镜,将该滤镜的输出传递到高斯模糊,最后将模糊的输出传回累加器:


 guard let touch = touches.first,
        event = event,
        coalescedTouches = event.coalescedTouchesForTouch(touch) else
    {
        return
    }


源代码

一如既往,这个项目的源代码可以在 我的 GitHub 存储库 中找到。享受!