CoreImage 变换
原文:Transitions with CoreImage
作者:Marin Todorov
译者:kmyhy
本教程兼容 Xcode 7/Swift 2。
在“iOS Animations by Tutorials ”的第 3 章 “转换动画” 中,我向你介绍了如何用内置的转换动画来渐入或渐出你的 view。
坦白讲,这种动画有一定限制。你可以选择以内容的位置进行动画,或者交叉溶解,或者反转动画。
CATransition 类中有一个属性 filter,允许你使用 CoreImage 滤镜来加强转换动画。
这个属性也可以用在 OS X——你可以创建任意 CoreImage 转换滤镜,然后赋给转换动画对象的 filter 属性。
在 iOS 中,filter 属性好像无所作为。网上有许多讨论,但这个属性就是不起作用。
幸好创建自己的视图转换动画用一个 CoreImage 转换滤镜来实现也不太难。在 Core Image 编程指南中包含了一个 如何做的 11 个步骤,将本期主题的理论基础。
在本教程的最后,你将实现一个 UIImageView 子类,你可以利用 CoreImage 来转换图片,效果如下:
开始
为了节省你创建 Xcode 项目和配置 UI 和自动布局是时间,我为你准备了开始项目。
下载 CITransitions-Starter.zip,解压缩,打开 CITransitions.xcodeproj 文件。
现在运行项目,什么也没有——你只能看到屏幕中间有一张图片:
除了屏幕上的这个 image view 外,这个项目只是一个普通的 single view 模板。如果你注意看项目导航器,你会发现几张图片——两张是图片本身,有 3 张是遮罩图片,你后面会用到它们:
接下来你将扩展 UIImageView ,为它添加一些功能。
TransitionImageView
在 Xcode 主菜单中选择 File/New/File… ,然后是 iOS/Source/Cocoa Touch Class。类名命名为 TransitionImageView,继承 UIImageView 并保存。
将文件内容修改为:
import UIKit
import CoreImageclass TransitionImageView: UIImageView {@IBInspectable var duration: Double = 2.0private let filter = CIFilter(name: "CICopyMachineTransition")!private var transitionStartTime: CFTimeInterval = 0.0private var transitionTimer: NSTimer?}
这里声明了 4 个新属性:
- duration 属性:动画时长,单位秒,它用 @IBInspectable 进行修饰,这样便于你在 IB 中修改它的值。
- filter 属性:CoreImage 滤镜,你将在每次转换时应用的滤镜。所谓的 copy machine 转换是一种经典的有趣的动画,没有它的教程,我们只好将就一下。你会发现这种滤镜很好玩。
- transitionStartTime 属性:用于保存当前动画什么时候开始的。
- transitionTimer 属性:为了制作自定义动画,我们必须使用定时器,通过定时器触发每一个动画帧的渲染。
很好的开头!再唠嗑一下关于滤镜的哪些事吧……
CoreImage 滤镜速成
CoreImage 滤镜很强大,能够让你很容易就创建出有趣的图形效果。滤镜通常会有一张原图,然后根据它输出修改后的图。例如,如果你对一张照片应用“褐化滤镜”,会制造出如下效果:
大部分滤镜你都通过可以修改一堆参数来满足你的需要。以上面的褐化滤镜为例,你可以指定原色应该被去掉多少饱和度,以及图片应当加上多少褐色。
除了简单滤镜外,还有一堆被称作“转换”的特殊滤镜。
它们能将一张图片转变成另一张图片。事实上,这些滤镜会产生从原图转换到目标图片的过程中多个帧,构成一个动画。
因此你可以创建一个转换滤镜,给它“指定原图和目标图,然后动画的 60% 处生成动画帧”。(或者动画过程中的任意百分点上)
要创建一个使用滤镜的动画——你需要不停地重复询问滤镜动画的下一帧,然后将这个帧作为 image view 类的显示图片:
听起来不错。
在开始写代码之前,你必须在 IB 中做些修改。打开 Main.storyboard - 你会看到这个 image view:
在做动画之前,你必须将这个 image view 对象的类型设置为你的新类 TransitionImageView。选中 image view,修改 Class 属性:
确认一下它没问题——回到属性检查器,看一下里面是不是能够看到你的 @IBInspectable duration 属性:
打开 ViewController.swift 为 image view 添加一个出口:
@IBOutlet weak var imageView: TransitionImageView!
回到 Main.storyboard ,按住 Ctrl 键从 ViewController 对象拖到 image view。在弹出菜单中选择 imageView,即可连接到该出口。
UI 完成后,你可以编写动画代码了。
配置转换滤镜
在 iOS 创建新的滤镜,需要预先定义它的参数集。这是很有必要的,因为你可以轻松地测试这些滤镜,将滤镜效果调整成你需要的。
对于一个转换滤镜来说,最起码的参数是原图和目标图。打开 TransitionImageView.swift 新增方法:
func transitionToImage(toImage: UIImage?) {guard let image = image, let toImage = toImage else {fatalError("You need to have set an image, provide a new image and a mask to fire up a transition")}filter.setValue(CIImage(image: image), forKey: kCIInputImageKey)filter.setValue(CIImage(image: toImage), forKey: kCIInputTargetImageKey)}
transitionToImage 方法只有一个参数,也就是目标图片。这个方法会对滤镜进行配置,并开始动画。
来看下代码。首先,你要确认 image view 的 image 属性是否已经设置,以及 toImage 参数是否不为空。
然后调用 setValue(_,forKey:) 方法,将 image 作为滤镜的原图, key 参数指定为 kCIInputImageKey。
注意 CoreImage 类使用的是 CIImage(而非 UIImage 或者 CGImageRef)。
最后设置滤镜的目标图,将 toImage 参数赋给(转换成 CIImage)key kCIInputTargetImageKey。
好!这样你就配置好了转换的原图和目标图。现在你需要创建一个定时器,开始获得动画帧并将它们提供给 image view。
创建一个 NSTimer 动画
在 transitionToImage: 方法中添加:
if let timer = transitionTimer where timer.valid {timer.invalidate()
}
这个判断语句会在 transitionToImage 方法被调用,也就是已经有一个动画还未完成时重置定时器。这里调用的是 timer.invalidate(),它会重置已有的动画,以便开始一个新的动画。
然后保存当前新动画的开始时间。继续添加代码:
transitionStartTime = CACurrentMediaTime()
CACurrentMediaTime() 会拿到当前绝对时间的秒数(小数部分则是精确到毫秒)。在计算动画进度时需要用到这个动画开始时间。
最后来触发动画定时器:
transitionTimer = NSTimer(timeInterval: 1.0/30.0,target: self, selector: Selector("timerFired:"),userInfo: toImage,repeats: true)
NSRunLoop.currentRunLoop().addTimer(transitionTimer!, forMode: NSDefaultRunLoopMode)
定时器的重复周期是 1/30 秒,也就是每秒触发 30 次。你可以调高这个帧率,但请注意,CoreImage 会执行你的滤镜,每秒执行的次数越多,对设备的考验就越大。
selector 参数中指定的 timerFired: 方法是 ViewController 类中的方法。你还没有这个方法,但等会就会添加它。
还有一个地方需要注意——每个 NSTimer 都会有一个 userInfo 属性。这个属性的类型是 AnyObject?。你可以赋给它任意类型。你可以用它来保存后面要用到的任何信息。就眼下来说,你需要保存转换的最后的 UIImage——当转换完成,你可以用它作为动画的最后一帧。
获取动画帧
接下来你需要添加一个方法,每当定时器触发时获取一个动画帧。添加一个空方法:
func timerFired(timer: NSTimer) {
}
首先需要知道转换动画已经完成了多久。你需要一个 0-1 之间的值,以表示动画完成进度。
在 timerFired: 方法中添加代码:
let progress = (CACurrentMediaTime() - transitionStartTime) / duration
首先算出从动画开始到现在经过了多少时间,然后除以整个动画时长 duration。你会得到一个 0.0-1.0 之间的数,表示转换过程的完成度 progress。
接下来需要让滤镜知道这个进度。添加这句:
filter.setValue(progress, forKey: kCIInputTimeKey)
将 progress 设置到 kCIInputTimeKey 这个 key 中,这样下次获取滤镜的输出时,它会知道你想要的是动画的哪一帧。
这样,你就完成了将滤镜的输出作为当前 image view 的 image 的工作。现在只需要从滤镜的 outputImage 属性中获得滤镜的处理结果,并赋给 image view 即可:
image = UIImage(CIImage: filter.outputImage!,scale: UIScreen.mainScreen().scale,orientation: UIImageOrientation.Up)
你基于 CIImage 滤镜的结果、正确的屏幕 scale 和方向,创建了一个新的 UIImage 对象。
这些代码已经给你的 image view 类添加了一个很酷的动画,但我们还是多写几句代码将 timerFired: 方法封装得更好一些。
添加代码:
if CACurrentMediaTime() > transitionStartTime + duration {image = timer.userInfo as? UIImagetimer.invalidate()
}
这会检查当前时间是否已经超出了动画的时长,如果是的话:
- 将转换动画的最后图片设置为定时器的 userInfo 属性(你之前已经为此实现保存过它)。
- 销毁定时器,这会停止向 image view 提供新的帧。
好了,就这样了!让我们来试试效果吧!
你的第一个 CoreImage 动画
没错,也许你已经猜到了,你还没有真正调用 transitionToImage 方法。
当你点击屏幕时,我们将 image view 变成现实另外一张图片。
打开 ViewController.swift ,在 viewDidLoad: 方法中添加:
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTap"))
)
在 view controller 的 View 上加一个手势识别器,当用户点击它时调用 didTap 方法。好的——现在来实现 didTap 方法和一个助手属性:
var currentImageName = ""func didTap() {currentImageName = (currentImageName == "Photo2.jpg") ? "Photo1.jpg" : "Photo2.jpg"imageView.transitionToImage(UIImage(named: currentImageName))
}
每当你点击屏幕,都会在 Photo1.jpg 和 Photo2.jpg 之间切换图片。这使得我们可以反复地测试它。
好了!一切准备就绪。
注意最后一点——从现在起你必须在真机上进行测试。模拟器对这种测试不太理想,因此请用真机测试。
运行 app,点击屏幕,你会看到你的第一个 CoreImage 转换:
这个滤镜会产生一种光线在复印机上扫描的效果——是不是很好玩?
接下来我们会创建更复杂的转换动画。现在,让我们看看如何调整当前转换动画的一些参数。
在你设置原图和目标图的代码下面,添加 2 行,设置滤镜的 extent 和颜色:
let extent = CIVector(x: 0.0, y: 0.0, z: image.size.width * 2.0, w: image.size.height * 2.0)let color = CIColor(red: 0.6, green: 1.0, blue: 1.0)filter.setValue(extent, forKey: kCIInputExtentKey)
filter.setValue(color, forKey: kCIInputColorKey)
- extent 定义一个向量,让滤镜效果覆盖图片的整个区域。例如,复印机的激光将扫过到达右边的整个路径,而不是默认的一半路径。在创建这个向量时,你需要将宽度和高度乘以 2,因为图片是 @2x 图。
- color 是亮蓝色,复印机激光是一种蓝色的冷色调。
最后设置了两个滤镜参数,key 分别为 kCIInputExtentKey 和 kCIInputColorKey。
再次运行项目,反复点击屏幕,看一下调整后的动画——你会发现亮蓝色的光束扫过了整个屏幕。
漂亮!
如果你想了解 Copy Machine 滤镜支持的所有参数,你可以看一下在线的 CICopyMachineTransition 文档。
更复杂的转换动画
有各种不同的转换滤镜。你可以用它们创建各种动画,然后调整它们的参数。有效的滤镜以及对应的设置请看在线文档。
接下来的内容将快速过一下 CIDisintegrateWithMaskTransition,这个滤镜能创建一种非常炫的转换。
打开 TransitionImageView.swift 添加一个属性:
@IBInspectable var maskImage: UIImage?
CIDisintegrateWithMaskTransition 使用了蒙层图片,因此你添加了一个属性用来保存这个蒙层图片。
然后找到 filter 属性声明,将它修改为:
private let filter = CIFilter(name: "CIDisintegrateWithMaskTransition")!
新的 filter 属性使用了一个不同的 name 参数,因此需要用这个 name 来初始化。
找到 transitionWithImage: 方法。将开头的 if 语句修改为检查 maskImage:
guard let image = image, let toImage = toImage, let maskImage = maskImage else {
然后删除设置 extent 和 color 参数的代码:
let extent = CIVector(x: 0.0, y: 0.0, z: image!.size.width * 2.0, w: image!.size.height * 2.0)
let color = CIColor(red: 0.6, green: 1.0, blue: 1.0)
filter.setValue(extent, forKey: kCIInputExtentKey)
filter.setValue(color, forKey: kCIInputColorKey)
CIDisintegrateWithMaskTransition 滤镜没有这两个参数,如果你不删除它们的话,CoreImage 会导致 app 崩溃。
在删除代码的地方,添加这句,以设置动画的蒙层图片:
filter.setValue(CIImage(image: maskImage), forKey: kCIInputMaskImageKey)
这个类的修改就完成了。在运行项目之前,回到 IB,将视图中 Mask Image 属性设置为 Mask2.png。
运行项目,你会发现动画变得如此的炫和与众不同:
CIDisintegrateWithMaskTransition 所做的不过是使用了这张图片作为蒙版:
然后从蒙层图片的暗部到亮部逐步显示目标图片的局部。下图显示了这个动画的步骤:
你可以看到,一开始只有蒙版中黑色和非常黑的部分开始显示。然后动画逐渐进入到深灰色、灰色和浅灰色的区域。最终,整个蒙层图片由所有灰色阴影部分加上白色部分构成。
这个蒙层图片很好地颜色了这个特效,因为它有一个颜色渐变,你可以清楚地看到这个动画是如何产生的。
你可以试一下另外两张蒙层图片。Mask1.jp 是这个样子:
它会产生这样的效果:
Mask3.jpg 是这个样子:
它产生一种极炫的下落的碎片动画效果——尝试一下吧!
如你所见——仅仅 CIDisintegrateWithMaskTransition 一个滤镜就能产生数不清的转换动画!而 CoreImage 还有很多这样的滤镜呢。
别忘了看一眼转换滤镜列表。
接下来做什么?
用 CoreImage 转换滤镜来创建 View Controller 转换是一个不错的想法。例如,你可以扩展第 23 章“交互式 UINavigationController 转换动画”中的项目,实现一个 CoreImage 转换动画。
这个任务并不难,因为这个项目中的手势识别器提供了平移手势的进度 0-1 之间的值。
你可以为新、老 view controller 截图,然后在 CoreImage 中使用这两张图,构成呈现动画。
如果你准备基于本教程的内容编写一些好玩的东东,请回复这封邮件或者 twitter 给我 @icanzilb。
CoreImage 变换相关推荐
- iOS之使用CoreImage进行人脸识别
更新 :应各位朋友的需求,补上了OC版本的demo, OC版下载地址 另外附上 : swift版下载地址 CoreImage是Cocoa Touch中一个强大的API,也是iOS SDK中的关键部分, ...
- 基于iOS用CoreImage实现人脸识别
2018-09-04更新: 很久没有更新文章了,工作之余花时间看了之前写的这篇文章并运行了之前写的配套Demo,通过打印人脸特征CIFaceFeature的属性,发现识别的效果并不是很好,具体说明见文 ...
- NVIDIA GPU的快速傅立叶变换
NVIDIA GPU的快速傅立叶变换 cuFFT库提供GPU加速的FFT实现,其执行速度比仅CPU的替代方案快10倍.cuFFT用于构建跨学科的商业和研究应用程序,例如深度学习,计算机视觉,计算物理, ...
- pytorch空间变换网络
pytorch空间变换网络 本文将学习如何使用称为空间变换器网络的视觉注意机制来扩充网络.可以在DeepMind paper 有关空间变换器网络的内容. 空间变换器网络是对任何空间变换的差异化关注的概 ...
- Python:matplotlib实践:直方图、散点图展示、变色、线条变换、点样式变换、添加名称、设置横纵轴范围、在一张图上显示多条线
直方图: ''' 来源:天善智能韦玮老师课堂笔记 作者:Dust ''' # 折线图.散点图 import matplotlib.pylab as pyl import numpy as npy x= ...
- RxJava 变换操作符Map
看下文档如下 通过对每个项目应用函数来转换Observable发出的项目 个人理解为转换类型 下面写一个把int 类型转换为String 类型的demo Observable.create(new O ...
- ORB_SLAM2中的Sim3变换
对于双目.RGB-D相机,可获得深度,因此不存在尺度问题,因此Sim3中的尺度s=1. (1)通过词袋加速算法实现当前帧.闭环帧的特征点的匹配,建立闭环帧的路标点和当前帧的特征点间的联系. (2)使用 ...
- 关于位姿变换的一点体会
关于位姿变换的一点体会 1.题外话 2.刚体的位姿变换 2.1 位姿变换的定义 2.2 旋转矩阵的具体形式 2.2.1 二维情况 2.2.2 三维情况 3.旋转方向 4.平移方向 1.题外话 对于刚体 ...
- 平方变换载波同步 matlab,matlab源码-costas载波同步环.docx
matlab源码-costas载波同步环.docx 在利用相干解调的数字通信系统中,载波同步是正确解调的前提,也是实际通信中的一项关键技术,没有载波同步就不可能正确的恢复出数字信号.常用的载波同步方法 ...
最新文章
- 思科3750开启策略路由功能
- 您遵循过这些Jenkins优秀实践吗?
- JSR-303校验类型
- 天池赛题解析:零基础入门语义分割-地表建筑物识别-CV语义分割实战(附部分代码)
- Python基础教程:为元组中的每一个元素命名
- 数据库索引统计信息不一致_列存储索引增强功能–克隆数据库中的索引统计信息更新
- Linux运维常用命令及知识
- 上班时真的很困怎么办
- AvalonDock 2.0+Caliburn.Micro+MahApps.Metro实现Metro风格插件式系统(菜单篇)
- JVM篇-JVM内存结构与存储机制
- [Matlab]使用textscan读取.csv文件时候只读取到了第一行
- 探测器类的电路设计流程框图
- 在线购物系统 实验七 顺序图
- Using 1.7 requires compiling with Android 4.4 (KitKat); currently using API XX
- 如何转载svg类的公众号文章
- Surface pro 4 使用心得
- 惠普笔记本突然读不到无线网卡
- Loadrunner之关联——用小故事理解
- 【FinE】CAPM和Carhart四因子模型实证
- 细粒度图像分析论文汇总