前言:Core Image是一个强大的框架,可让您轻松地将过滤器应用于图像。您可以获得各种各样的效果,如修改活力,色调或曝光。它可以使用CPU或GPU来处理图像数据,并且速度非常快 - 足以实现视频帧的实时处理!
核心图像滤镜也可以链接在一起,以一次将多个效果应用于图像或视频帧。多个滤波器被组合成应用于图像的单个滤波器。与通过每个过滤器一次处理图像相比,这样做非常有效。
入门
在开始之前,让我们来讨论Core Image框架中的一些最重要的类:
CIContext。核心图像的所有处理都以CIContext完成。这与Core Graphics或OpenGL上下文有些相似。
CIImage。该类保存图像数据。它可以从UIImage,从图像文件或从像素数据创建。
CIFilter。CIFilter类有一个字典,用于定义它所代表的特定过滤器的属性。过滤器的例子是振动,颜色反转,裁剪等等。
基本图像过滤
通过简单地运行您的图像CIFilter并在屏幕上显示图像来开始。每次想要将CIFilter应用于图像时,有4个步骤:
1、创建一个CIImage对象。CIImage有几种初始化方法,包括:CIImage(CIImage(contentsOf: ),CIImage(data :),CIImage(CGImage :),CIImage(bitmapData:bytesPerRow:size:format:colorSpace :)等几个。最常用的是用CIImage(contentsOf: )
2、创建CIContext。CIContext可以是基于CPU或GPU的。CIContext初始化相对耗费资源,因此您可以重用它,而不是一遍又一遍地创建它。输出CIImage对象时,您将始终需要一个。
3、创建一个CIFilter。创建过滤器时,您可以配置依赖于您使用的过滤器的许多属性。
4、获取过滤器输出。过滤器为您提供输出图像作为CIImage - 您可以使用CIContext将其转换为UIImage,如下所示
// 1
let fileURL = Bundle.main.url(forResource: "beauty", withExtension: "jpg")
// 2
let beginImage = CIImage(contentsOf: fileURL!)
// 3
let filter = CIFilter (name: "CISepiaTone")
filter?.setValue(beginImage, forKey: kCIInputImageKey)
filter?.setValue(0.5, forKey: kCIInputIntensityKey)
// 4
let newImage = UIImage(ciImage: (filter?.outputImage)!)
self.imageView.image = newImage;
我们先来看看这一节:
1、此行创建一个<code>NSURL</code>对象,该对象保存图像文件的路径。
2、接下来,使用<code>CIImage(contentsOf: )</code>构造函数创建您的CIImage。
3、接下来,您将创建您的<code>CIFilter</code>对象。<code>CIFilter</code>构造函数使用过滤器的名称,并指定该过滤器的键和值的字典。每个过滤器将有自己唯一的密钥和一组有效的值。所述<code>CISepiaTone</code>过滤器只需两个值:<code>KCIInputImageKey</code>(一个CIImage)和在0和1之间的<code>kCIInputIntensityKey</code> ,你给该值0.5。大多数过滤器具有默认值,如果没有提供值,将使用该值。一个例外是<code>CIImage</code>,这是必须提供的,因为没有默认。
4、将<code>CIImage</code>从过滤器中恢复与使用该<code>outputImage</code>属性一样简单。一旦输出<code>CIImage</code>,您将需要将其转换为<code>UIImage</code>。该<code>UIImage(ciImage:)</code>构造函数转换了<code>CIImage</code>到<code>UIImage</code>。一旦将其转换为<code>UIImage</code>,您只需将其显示在您之前添加的<code>imageView</code>中。
运行该项目,您将看到由深褐色滤镜过滤的图像。
置于上下文
在您继续了解之前,您应该了解一个优化。
我之前提到你需要一个CIContext应用CIFilter,但在上面的例子中没有提到这个对象。事实证明,<code>UIImage(ciImage:)code</code>构造函数为您做所有的工作。它创建CIContext并使用它来执行过滤图像的工作。这使得使用Core Image API非常简单。
有一个主要的缺点 - CIContext每次使用时都会创建一个新的。CIContext实例旨在可重用以提高性能。如果要使用滑块来更新过滤器值,就像在本教程中所做的那样,每次更改过滤器时都会创建一个新的CIContext将太慢了。
我们这样做是正确的。从viewDidLoad()添加的代码中删除步骤4 ,并将其替换为以下内容:
// 1
let context = CIContext(options:nil)
// 2
let cgimg = context.createCGImage(filter!.outputImage!, from: filter!.outputImage!.extent)
// 3
let newImage = UIImage(cgImage: cgimg!)
self.imageView.image = newImage;
再次,我们一起来看看这一节。
在这里,您设置CIContext对象并使用它来绘制CGImage。该CIContext(options:)构造采用指定的选项一个NSDictionary如色彩格式,或上下文是否应在CPU或GPU上运行。对于这个应用程序,默认值是好的,所以你传递为nil为该参数。
<code>createCGImage(outputImage:from:)</code>使用提供的CIImage在上下文中调用将返回一个新的CGImage实例。
接下来,您使用<code>UIImage(cgImage:)</code>构造函数从新创建的CGImage创建UIImage,而不是像以前一样直接从CIImage创建。注意,在完成它之后,不需要明确地释放CGImage,就像在Objective-C中一样。在Swift中,ARC可以自动释放Core Foundation对象。
构建和运行,并确保它像以前一样工作。
在这个例子中,自己处理CIContext的创建并没有太多的区别。但在下一节中,您将看到为什么这对于性能很重要,因为您实现了动态修改过滤器的功能!
更改过滤器值
下面增加滑块,每次滑块更改时,都需要使用不同的值重做图像过滤器。但是,您不想重做整个过程,这将是非常低效的,并且需要太长时间。您将需要更改类中的一些内容,以便您可以保留在viewDidLoad方法中创建的一些对象。
如果为了重新使用CIContext,而每次重新创建它程序将运行非常缓慢。
添加一些实例变量才能完成此任务。将以下三个属性添加到ViewController类中:
var context: CIContext!
var filter: CIFilter!
var beginImage: CIImage!
更改代码,因此<code>viewDidLoad()</code>使用这些属性,而不是声明新的局部变量,如下所示:
beginImage = CIImage(contentsOf: fileURL!)
filter = CIFilter (name: "CISepiaTone")
filter?.setValue(beginImage, forKey: kCIInputImageKey)
filter?.setValue(0.5, forKey: kCIInputIntensityKey)
let outputImage = filter.outputImage
context = CIContext(options:nil)
let cgimg = context.createCGImage(filter!.outputImage!, from: filter!.outputImage!.extent)
实现changeValue方法。在CIFilter字典中改变<code>inputIntensity</code>值。
一旦你改变了这个值,你需要重复几个步骤:
1、从CIFilter获取输出CIImage。
2、将CIImage转换为CGImage。
3、将CGImage转换为UIImage,并将其显示在图像视图中。
将创建一个方法<code>amountSliderValueChanged(sender :)</code>:
@IBAction func amountSliderValueChange(_ sender: UISlider) {
let sliderValue = sender.value
filter.setValue(sliderValue, forKey: kCIInputIntensityKey)
let outputImage = filter.outputImage
let cgimg = context.createCGImage(outputImage!, from: outputImage!.extent)
let newImage = UIImage(cgImage: cgimg!)
self.imageView.image = newImage
}
老相片效果
在这个Demo中,会得到一个更精致的老照片效果,完成与棕褐色,一点噪音和一些晕影
func oldPhoto (img: CIImage, withAmount intensity: Float) -> CIImage {
//1 CISepiaTone 棕褐色调
let sepia = CIFilter(name: "CISepiaTone")
sepia?.setValue(img, forKey: kCIInputImageKey)
sepia?.setValue(intensity, forKey: "inputIntensity")
//2 设置一个过滤器,创建一个随机噪声模式
let random = CIFilter(name: "CIRandomGenerator")
//3 改变随机噪声发生器的输出
let lighten = CIFilter(name:"CIColorControls")
lighten?.setValue(random?.outputImage, forKey:kCIInputImageKey)
lighten?.setValue(1 - intensity, forKey:"inputBrightness")
lighten?.setValue(0, forKey:"inputSaturation")
//4 cropping(to rect: CGRect)输出CIImage并将其作用到所提供的rect
let croppedImage = lighten?.outputImage?.cropping(to: beginImage.extent)
//5 将棕褐色滤镜的输出与CIRandomGenerator滤镜的输出相结合。
let composite = CIFilter(name:"CIHardLightBlendMode")
composite?.setValue(sepia?.outputImage, forKey:kCIInputImageKey)
composite?.setValue(croppedImage, forKey:kCIInputBackgroundImageKey)
//6 合成输出上运行晕影滤镜(vignette filter),使照片的边缘变暗
let vignette = CIFilter(name:"CIVignette")
vignette?.setValue(composite?.outputImage, forKey:kCIInputImageKey)
vignette?.setValue(intensity * 2, forKey:"inputIntensity")
vignette?.setValue(intensity * 30, forKey:"inputRadius")
//7 返回滤镜的输出
return vignette!.outputImage!
}
效果图:
解析以上代码:
1、像在简单的场景中所做的一样,设置棕褐色滤镜。您在方法中传入浮点值以设置深色效果的强度。该值将由滑块提供。
2、设置一个过滤器,创建一个如下所示的随机噪声模式:
它不需要任何参数。您将使用这种噪音模式将纹理添加到最终的“旧照片”外观。
3、改变随机噪声发生器的输出。你想把它改成灰度,并减轻一点点,所以效果不那么戏剧化。您会注意到,输入图像键被设置为随机过滤器的outputImage属性。这是一个方便的方式来传递一个过滤器的输出作为下一个的输入。
4、 cropping(to rect: CGRect)输出CIImage并将其作用到所提供的rect。在这种情况下,您需要裁剪CIRandomGenerator过滤器的输出,因为它无限制地打砖块。如果您在某些时候没有裁剪,就会出现一个错误,表示过滤器具有“无限长度”。CIImages实际上并不包含图像数据,它们描述了创建它的“配方”。直到你在CIContext上调用一个方法来实际处理数据。
5、将棕褐色滤镜的输出与CIRandomGenerator滤镜的输出相结合。该过滤器执行与Photoshop图层中的“硬光”设置完全相同的操作。使用Core Image可以实现Photoshop中的大多数滤镜选项。
6、在此合成输出上运行晕影滤镜,使照片的边缘变暗。您正在使用滑块的值来设置此效果的半径和强度。
7、返回最后一个过滤器的输出。