ios开发高级进阶群号:362525707
踩着巨人的肩膀,笑傲江湖!
资源下载地址:http://download.youkuaiyun.com/detail/lfr_dev/6968589
Core Image是一个很强大的框架。它可以让你简单地应用各种滤镜来处理图像,比如修改鲜艳程度, 色泽, 或者曝光。 它利用GPU(或者CPU,取决于客户)来非常快速、甚至实时地处理图像数据和视频的帧。多个Core Image滤镜可以叠加在一起,从而可以一次性地产生多重滤镜效果。这种多重滤镜的优点在于它可以生成一个改进的滤镜,从而一次性的处理图像达到目标效果,而不是对同一个图像顺序地多次应用单个滤镜。每一个滤镜都有属于它自己的参数。这些参数和滤镜信息,比如功能、输入参数等都可以通过程序来查询。用户也可以来查询系统从而得到当前可用的滤镜信息。到目前为止,Mac上只有一部分Core Image滤镜可以在iOS上使用。但是随着这些可使用滤镜的数目越来越多,API可以用来发现新的滤镜属性。
Core Image 总览
开始之前,让我们谈谈Core Image框架中最重要的几个类:
- CIContext. 所有图像处理都是在一个CIContext 中完成的,这很像是一个Core Image处理器或是OpenGL的上下文。
- CIImage. 这个类保存图像数据。它可以从UIImage、图像文件、或者是像素数据中构造出来。
- CIFilter. 滤镜类包含一个字典结构,对各种滤镜定义了属于他们各自的属性。滤镜有很多种,比如鲜艳程度滤镜,色彩反转滤镜,剪裁滤镜等等。
在新建一个项目过程中,你会依次用到这些类。
让我们开始吧
打开Xcode,创建一个空项目。输入ios_CoreImage作为产品的名字。选择iPhone作为设备类型。
首先,让我们导入Core Image框架。在Mac上,这个过程是QuartzCore框架的一部分;但是在iOS上,这个是单独的一个框架。在左侧文件导航栏中,进入项目文件夹。选择Build Phases标签页,扩展Link Binaries和Library group, 点击“+”来添加按钮;找到CoreImage框架并且双击完成添加。
第二步,下载demo, 把1.png添加到项目中,我们的创建设置就完成了。
之后,新建LFRViewController并添加为self.window的rootViewController; 并在LFRViewController里面实例化一个UIIimageView对象。
编译运行来确保到目前为止每一步都是正确的。如果一切正常,你将会看到一个空屏幕。这时,我们的初始化设置就完成了,下面我们就进入Core Image部分!
基本的图像滤镜
先尝试一下,我们先简单的让图像通过一个CIFilter 之后显示在屏幕上。每一次当我们想应用一个CIFilter的时候都要有以下四个步骤:1、创建一个 CIImage 对象: CIImage 有如下的初始化方法: imageWithURL:, imageWithData:, imageWithCVPixelBuffer:, 和 imageWithBitmapData:bytesPerRow:size:format:colorSpace:。但是大多数时候你只会经常用到imageWithURL。
2、创建一个 CIContext: 一个 CIContext 可以是基于CPU或是GPU的。它可以被重用,所以你不用每次都创建一个。但是当输出CIImage对象的时候你至少一定会需要一个CIContext。
3、创建一个CIFilter: 当你创建滤镜的时候,你可以在上面配置一定数量的属性。具体的属性取决于你所要用的滤镜。
4、输出滤镜:这个滤镜会输出一个图像成为CIImage。 你可以用CIContext把它转化为一个UIImage ,具体过程如下。
//小编在这里只写关键代码了,如果有什么不懂的,可以加群讨论。
@interface LFRViewController ()
{
UIImageView *_imageView;
}
- (void)viewDidLoad
{
[superviewDidLoad];
_imageView = [[UIImageViewalloc]initWithFrame:CGRectMake(0,0,self.view.frame.size.width,429)];
[self.view addSubview:_imageView];
//1
NSString *filePath = [[NSBundlemainBundle]pathForResource:@"1"ofType:@"png"];
NSURL *fileNameAndPath = [NSURLfileURLWithPath:filePath];
//2
CIImage *beginImage = [CIImageimageWithContentsOfURL:fileNameAndPath];
//3
CIFilter *filter = [CIFilterfilterWithName:@"CISepiaTone"
keysAndValues:kCIInputImageKey,beginImage,@"inputIntensity",@1.0,nil];
CIImage *outputimage = [filter outputImage];
//4
UIImage *newImage = [UIImageimageWithCIImage:outputimage];
_imageView.image = newImage;
}
让我们依次看看这些代码都做了什么事情
- 前两行创建了一个NSURL 对象, 包含指向图形文件的路径。
- 下面,用imageWithContentsOfURL方法创建CIImage。
- 之后,创建CIFilter对象。一个 CIFilter 构造函数有两个输入,分别是滤镜的名字,还有规定了滤镜属性的键值和取值的字典。 每一个滤镜会有它自己唯一的键值和一组有效的取值。CISepiaTone 滤镜只能选两个值: KCIInputImageKey (一个CIImage) 和 @”inputIntensity”。 后者是一个封装成NSNumber (用新的文字型语法)的浮点小数,取值在0和1 之间。大部分的滤镜有默认值,只有CIImage是个例外。你必须提供一个值给它,因为它没有默认值。从滤镜中导出CIImage很简单,只需要用outputImage方法。
- 一旦你有了导出的 CIImage,你就可以把它转化为一个 UIImage。 在新的iOS6中,UIImage 方法+ imageWithCIImage方法可以实现从CIImage 到UIImage 到转化。一旦转化完成,我们就可以让UIImage 显示在之前添加的图像视图里。
编辑运行项目,你将会看到你的图片如下图一般,已经被墨色调滤镜处理过。恭喜你,你已经成功掌握并运用了CIImage和CIFilters。
把它放在上下文中
在进行下一步之前,有一个优化的方法很实用。我前面提到过,你需要一个CIContext来进行CIFilter,但是在上面的例子中我们没有提到这个对象。因为我们调用的UIImage方法(imageWithCIImage)已经自动地为我们完成了这个步骤。它生成了一个CIContext并且用它来处理图像的过滤。这使得调用Core Image的接口变得很简单。
CIImage *beginImage = [CIImageimageWithContentsOfURL:fileNameAndPath];
//1
CIContext *context = [CIContextcontextWithOptions:nil];
CIFilter *filter = [CIFilterfilterWithName:@"CISepiaTone"
keysAndValues:kCIInputImageKey,beginImage,@"inputIntensity",@1.0,nil];
CIImage *outputimage = [filter outputImage];
//2
CGImageRef cgImg = [context createCGImage:outputimage fromRect:[outputimage extent]];
//3
UIImage *newImage = [UIImageimageWithCGImage:cgImg];
_imageView.image = newImage;
//4
CGImageRelease(cgImg);
再让我逐步解释一下这部分代码
- 在这部分代码中,你创建了CIContext对象。CIContext 构造函数的输入是一个NSDictionary。 它规定了各种选项,包括颜色格式以及内容是否应该运行在CPU或是GPU上。对于这个应用程序,默认值是可以用的。所以你只需要传入nil作为参数就好了。
- 在这里你用上下文对象里的一个方法来画一个CGImage。 调用上下文中的createCGImage:fromRect:和提供的CIImage可以生成一个CGImageRef。
- 下面,你用UIImage + imageWithCGImage,从CGImage中创建一个UIImage。
- 最后,开放 CGImageRef接口。 CGImage 是一个C接口,即使有ARC,也需要你自己来做内存管理。
编译运行,确保正常工作。
在这个例子中,添加CIContext的创建 和你自己来创建的区别不大。但是在下一部分中,你将会看到当你实现动态改变滤镜参数的时候的重大性能差别。
改变滤镜的取值
上面可以看到,Core Image滤镜很好用,但是这些只是非常初级的应用。让我们添加一个滑动条使得我们能够实时动态地调整图像设置。
@interface LFRViewController ()
{
UIImageView *_imageView;
UISlider *_slider;
CIContext *_context;
CIFilter *_filter;
CIImage *_beginImage;
}
现在,你将实现changeValue方法来实现改变CIFilter 字典中@”inputIntensity”键值的功能。在我们实现了这个改变之后,你还需要重复如下一些步骤:
- 从CIFilter 中得到CIImage
- 把CIImage转化成 CGImageRef.
- 把CGImageRef 转化成UIImage, 在图像视图中显示出来。
- (void)sliderValueChanged:(UISlider *)sender
{
float slideValue = sender.value;
[_filter setValue:@(slideValue)
forKey:@"inputIntensity"];
CIImage *outputImage = [_filteroutputImage];
CGImageRef cgimg = [_contextcreateCGImage:outputImage
fromRect:[outputImageextent]];
UIImage *newImage = [UIImageimageWithCGImage:cgimg];
_imageView.image = newImage;
CGImageRelease(cgimg);
}
你可以从滑动条中获取浮点数。滑动条有相应的默认设置 – 最小值0,最大值0,默认值0.5。这刚好是这个CIFilter的合理取值,简直太方便了!
CIFilter 有相应的方法可以任由我们在字典中设置不同键值的取值。在这里你只需要把@”inputIntensity” 键设置成一个NSNumber 对象,它的取值是你从滑动条上得到的任意浮点数。
代码的其他部分应该看上去很像,因为都是遵循和viewDidLoad方法同样的逻辑。你将会反复重用这些代码。从现在开始,你将用changeSlider方法来为UIImageView提供CIFilter输出。
编译运行,你将会得到一个可以实时改变图片墨色调数值的滑动条!
从相册中读取照片
1、添加代理协议<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
- (void)loadPhoto:(id)sender
{
UIImagePickerController *pickerViewController = [[UIImagePickerControlleralloc]init];
pickerViewController.delegate = self;
[selfpresentViewController:pickerViewControlleranimated:YEScompletion:nil];
}
3、实现回调方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[selfdismissViewControllerAnimated:YEScompletion:nil];
UIImage *getImage = [info objectForKey : UIImagePickerControllerOriginalImage ];_beginImage = [CIImageimageWithCGImage:getImage.CGImage];
[_filtersetValue:_beginImageforKey:kCIInputImageKey];
[selfsliderValueChanged:_slider];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[selfdismissViewControllerAnimated:YEScompletion:nil];
}
注意,在字典中有一个字段就是专门为被选择的原始图片而设置的。这个字段就正是你需要取出并且过滤的!
既然我们已经知道怎么选取一个图片,那么我们怎么设置CIImage beganImage来调用这个图片呢?
你需要从你选择的图片中创建一个新的CIImage。在UIImagePickerControllerOriginalImage键值是个常数的情况下,你可以通过寻找字典中的取值得到图片的 UIImage 代理。注意最好用一个常数,而不是一个硬编码的字符串,因为Apple可以在未来改变键的名字。从UIImagePickerController代理协议参考中你可以找到所有的常数键。
你需要转化这些成为一个 CIImage,但是并没有一个方法可以把一个 UIImage转化成一个CIImage。然而你有[CIImage imageWithCGImage:] 方法。它可以通过调用UIImage.CGImage来从UIImage中得到CIImage。那么你完全可以做一样的事情!
于是你设置滤镜字典中的相应键,使得导入的图片正是你刚刚常见的CIImage。
最后一行可能看起来会很奇怪。还记得我是怎么阐述changeView的代码是用最新的值来运行滤镜,并且根据运行结果更新图像视图的吗?
你需要再做一遍这个工作,所以你只需要调用一遍changeValue方法。即使滑动条的值没有改变,你仍然可以使用哪个方法的代码来完成这个工作。
你可以拆开那部分代码形成单独的方法。而且随着你做的事情越来越复杂,你也希望用这种方式尽量避免混淆。但是就当前这个问题而言,你的目的只是想用changeValue方法,所以你传入amountSlider,得到正确的值就好了。
编译运行,你现在就可以编辑更新你相册里的任意图片或照片了。
在把你的图片做了墨色调处理之后,怎么保持它呢。你可以截屏,但是你没那么土!让我们学学如何保存处理后的图片到你的相册里。
保存到相册
为了保存到相册,你需要一个AssetsLibrary框架。进入到项目容器里,选择Build Phases标签页,扩展Link Binaries和Library group, 点击“+”来添加按钮。找到AssetsLibrary框架,选择进行添加。
之后把下面的#import 内容添加到ViewController.m的顶部。
#import <AssetsLibrary/AssetsLibrary.h>
你需要明白一件事情,那就是当你保存一张照片到相册的时候,即使你退出了这个应用,这个过程仍然可以继续。
这点可能会导致一些问题,因为GPU在当你切换应用的时候会停止当前的工作。如果照片还没有保存完毕就退出了程序,那可能以后就找不到这个要保存的照片了。
对于这个问题的解决方法是利用CPU的CIRendering上下文。然而默认设备是GPU,而且GPU比CPU快很多。所以你其实可以创建第二个CIContext,只为了保存这个图片。
让我们添加一个新按钮来实现对当前编辑照片的保存, 添加一个新按钮,标记为“保存”( Save to Album )。
- (void)saveToAlbum
{
//1
CIImage *saveToSave = [_filteroutputImage];
// 2
CIContext *softwareContext = [CIContext
contextWithOptions:@{kCIContextUseSoftwareRenderer :@(YES)} ];
// 3
CGImageRef cgImg = [softwareContext createCGImage:saveToSave
fromRect:[saveToSaveextent]];
// 4
ALAssetsLibrary* library = [[ALAssetsLibraryalloc]init];
[library writeImageToSavedPhotosAlbum:cgImg
metadata:[saveToSaveproperties]
completionBlock:^(NSURL *assetURL,NSError *error) {
// 5
CGImageRelease(cgImg);
}];
}
在这段代码中:
- 从滤镜中得到CIImage输出
- 创建一个新的、基于软件的CIContext
- 生成CGImageRef.
- 保存CGImageRef 到图片库
- 释放CGImage。最后一步在回调部分发生,使得只有在完成之后才会用到它。
编译并且在真正的设备上运行这个应用,你就可以永久保存你想要的图片到相册里。
图像元数据怎么处理呢?
让我们简单谈谈图像的元数据。移动电话上拍摄的图像文件有一系列的数据相关联,比如GPS坐标,图像格式,图像朝向等等。具体来说,图像的朝向是你需要保存的数据。加载原始图像到CIImage,转化为CGImage, 进而转化为UIImage的过程去除掉了原始图像的元数据。为了保存图像的朝向,你需要记录并且恢复这些相关图像信息到UIImage。你可以通过添加一个新的私有实例变量到ViewController.m当中来达到这个目的。
下一步,当从相册里加载原始图像的时候,可以通过imagePickerController: didFinishPickingMediaWithInfo方法设定相应的元数据值。把下面几行代码加入到 “beginImage = [CIImage imageWithCGImage:gotImage.CGImage]” 这一行代码的前面:
_orientation = getImage.imageOrientation;
最终,改变amountSliderChanged中的代码,创建imageView对象中设定的UIImage:
UIImage *newImage = [UIImage imageWithCGImage:cgimg scale:1.0 orientation:orientation];
现在,如果你用非默认的朝向照一张照片, 这个朝向信息将会被保存下来。
还有其他什么滤镜可以用吗?
CIFilter 接口在Mac OS上有130个滤镜,外加可以定制滤镜的能力。 在iOS6中,有93个或更多;但是目前还不能实现在iOS平台上对滤镜的定制。希望以后可以做到。
为了找到可用的滤镜信息,你可以利用 [CIFilter filterNamesInCategory:kCICategoryBuiltIn] 方法。 这个方法会返回一列可用滤镜的名字。而且,每一个滤镜都有一个属性方法来返回一个包含滤镜信息的字典结构。这些信息包括滤镜的名字,滤镜的分类,滤镜的输入以及输入的默认值和可接受的值范围。
让我们为你的类整理出一个方法。调用这个方法可以在日志文件中打印出所有可用滤镜信息。把下面这个方法加入到viewDidLoad的上面:
@interface LFRViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
{
UIImageView *_imageView;
UISlider *_slider;
CIContext *_context;
CIFilter *_filter;
CIImage *_beginImage;
UIImageOrientation orientation;//new
}
下一步,当从相册里加载原始图像的时候,可以通过 imagePickerController: didFinishPickingMediaWithInfo 方法设定相应的元数据值。把下面几行代码加入到 “beginImage = [CIImage imageWithCGImage:gotImage.CGImage]” 这一行代码的前面:_orientation = getImage.imageOrientation;
UIImage *newImage = [UIImageimageWithCGImage:cgimgscale:1.0orientation:_orientation];
现在,如果你用非默认的朝向照一张照片, 这个朝向信息将会被保存下来。
还有其他什么滤镜可以用吗?
CIFilter 接口在Mac OS上有130个滤镜,外加可以定制滤镜的能力。 在iOS6中,有93个或更多;但是目前还不能实现在iOS平台上对滤镜的定制。希望以后可以做到。
为了找到可用的滤镜信息,你可以利用 [CIFilter filterNamesInCategory:kCICategoryBuiltIn] 方法。 这个方法会返回一列可用滤镜的名字。而且,每一个滤镜都有一个属性方法来返回一个包含滤镜信息的字典结构。这些信息包括滤镜的名字,滤镜的分类,滤镜的输入以及输入的默认值和可接受的值范围。
让我们为你的类整理出一个方法。调用这个方法可以在日志文件中打印出所有可用滤镜信息。把下面这个方法加入到viewDidLoad的上面:
-(void)logAllFilters {
NSArray *properties = [CIFilterfilterNamesInCategory:
kCICategoryBuiltIn];
NSLog(@"%@", properties);
for (NSString *filterNamein properties) {
CIFilter *fltr = [CIFilterfilterWithName:filterName];
NSLog(@"%@", [fltrattributes]);
}
}
这个方法从 filterNamesInCategory 方法中获取可用滤镜的名字,先打印名字,之后对于在列表上的每一个名字,创建一个相应的滤镜,并且记录该滤镜中的属性字典。之后在 viewDidLoad 的底部调用下面这个方法:[selflogAllFilters];
更复杂的滤镜链
既然我们已经学习了iOS6平台上所有可用的滤镜, 我们可以进一步看看如何创建一个更复杂的滤镜链。为了达到这个目的,我们需要创建一个专门的方法来处理CIImage。它将导入CIImage,过滤处理,之后返回一个CIImage。添加如下的方法:
-(CIImage *)oldPhoto:(CIImage *)img withAmount:(float)intensity {
// 1
CIFilter *sepia = [CIFilterfilterWithName:@"CISepiaTone"];
[sepia setValue:img forKey:kCIInputImageKey];
[sepia setValue:@(intensity)forKey:@"inputIntensity"];
// 2
CIFilter *random = [CIFilterfilterWithName:@"CIRandomGenerator"];
// 3
CIFilter *lighten = [CIFilterfilterWithName:@"CIColorControls"];
[lighten setValue:random.outputImageforKey:kCIInputImageKey];
[lighten setValue:@(1 - intensity)forKey:@"inputBrightness"];
[lighten setValue:@0.0forKey:@"inputSaturation"];
// 4
CIImage *croppedImage = [lighten.outputImageimageByCroppingToRect:[_beginImageextent]];
}