很多UIView的子类,例如UIButton或者UIlabel,都知道如何绘制自己;不过迟早,你都会想绘制一些自己想要的效果。你可以通过一些已有的类在代码中绘制一幅图片,然后在自己的界面上展示出来,例如UIImageVIew和UIButton。单纯一个UIView就是只与绘制有关,它给你了很大的空间来绘画;你的代码决定了这个视图怎么绘制自己,最终怎么在你界面上展示。
UIImage和UIImageView
iOS系统支持很多标准的图片格式:TIFF、JPEG、GIF、PNG等。当一张图片被包含在我们的app 包内,iOS系统特别地,会对PNG文件提供更加友好的支持,不只是因为系统会对它进行压缩处理,还有在不同分辨率下的对图片的选取和展示都做了很多工作,所以我们应该优先选择PNG格式图片。我们可以通过 imageNamed: 这个UIImage类提供的方法获取app包内的图片,这个方法会从两个地方寻找指定的图片:
app包顶级目录
系统会通过提供的图片名字,名字是大小写敏感的,以及包括图片的类型,在app包中寻找。如果没有提供类型,默认是png格式。
Asset catalog 资源目录
它会通过提供的名字,在这个资源目录中寻找匹配的图片集。如果名字带有文件后缀,就不会在这里查找,以便旧代 码中,如果把图片移动到这个目录仍然能够正常工作。这个目录的查找优先级比上面的查找高,也就意味着,如果在这个 资源目录下找到了匹配的图片,方法就会返回,而不会再去app包顶级目录中查找。
可调整大小的Images
可以通过向一个UIImage发送 resizableImageWithCapInsets:resizingMode: 消息,来把图片转换成可调整大小的图片。capInsets参数是一个UIEdgeInsets类型的结构体,由四个浮点型数字组成:top,left,bottom,right。它们代表着从图片边缘向内的距离。在一个比图片要大的上下文中,可调整大小的Image有两种工作模式,通过 resizingMode: value: 指定
UIImageResizingModeTile
在上面capInsets 指定的内部矩形区域会平铺在内部,每一个边缘由对应边的矩形区域平铺而成,而外面的四个角落的矩形不变。
UIImageResizingModeStretch
内部的矩形会被拉伸一次来填充内部,每个边缘由对应变的矩形区域拉伸而成,而外面的四个角落的矩形不变。
例如:假设 self.iv 是一个有固定长宽的UIImageView,contentMode是UIViewContentModeScaleToFill。
(1)设置capInsets 为 UIEdgeInsetsZero
1
2
3
4
|
UIImage* mars = [UIImage imageNamed:@ "Mars" ];
UIImage* marsTiled = [mars resizableImageWithCapInsets: UIEdgeInsetsZero
resizingMode: UIImageResizingModeTile];
self .iv.image = marsTiled;
|
(2)
1
2
3
4
5
6
|
UIImage* marsTiled = [mars resizableImageWithCapInsets:
UIEdgeInsetsMake(mars.size.height/4.0,
mars.size.width/4.0,
mars.size.height/4.0,
mars.size.width/4.0)
resizingMode: UIImageResizingModeTile];
|
(3)常用的拉伸策略是把几乎是原始图片的一半作为capinset,仅仅在中间留出1到2像素来填充整个内部。
1
2
3
4
5
6
|
UIImage* marsTiled = [mars resizableImageWithCapInsets:
UIEdgeInsetsMake(mars.size.height/2.0 - 1,
mars.size.width/2.0 - 1,
mars.size.height/2.0 - 1,
mars.size.width/2.0 - 1)
resizingMode: UIImageResizingModeStretch];
|
在最新的Xcode5 中,我们可以不用代码来配置一个可调整大小的图片,仅仅通过Xcode5提供的一个 asset catalogs 功能,而不用多次编写同样的代码,这个功能仅在ios7.0以上版本可用。
图片的渲染模式
1
2
3
4
5
6
|
- ( void ) drawRect: (CGRect) rect {
UIBezierPath* p =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
}
|
现在我用Core Graphics 实现同样的效果;这样需要我首先拿到一个当前上下文的引用:
1
2
3
4
5
6
|
- ( void ) drawRect: (CGRect) rect {
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);
}
|
接下来,我会在UIView子类中实现 drawLayer:inContext:。这种情况下,我们手中的上下文引用并不是当前上下文,所以我需要用UIKit把它转换成当前上下文:
1
2
3
4
5
6
7
8
|
- ( void )drawLayer:(CALayer*)lay inContext:(CGContextRef)con {
UIGraphicsPushContext(con);
UIBezierPath* p =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
UIGraphicsPopContext();
}
|
为了在drawLayer:inContext:中使用Core Graphics,我仅仅需要简单地保留一个我持有的上下文即可:
1
2
3
4
5
|
- ( void )drawLayer:(CALayer*)lay inContext:(CGContextRef)con {
CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);
}
|
最后,为了完整性,让我们创建一个蓝色圆形的UIImage对象。我们可以在任何时间(我们不需要等待某些特定方法被调用)以及在任何类(我们不需要在UIView的子类)中创建。创建的UIImage你可以在任何地方正常使用,例如,你可以把它放到一个可见的UIImageView中当做图片展示,或者你可以把它保存在一个文件中,或者你可以在其他的绘制中使用(下一节介绍)。
首先,我使用UIKit绘制我的图片:
1
2
3
4
5
6
7
8
|
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO , 0);
UIBezierPath* p =
[UIBezierPath bezierPathWithOvalInRect:CGRectMake(0,0,100,100)];
[[UIColor blueColor] setFill];
[p fill];
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// im is the blue circle image, do something with it here ...
|
下面是使用Core Graphics实现的:
1
2
3
4
5
6
7
|
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100,100), NO , 0); CGContextRef con = UIGraphicsGetCurrentContext();
CGContextAddEllipseInRect(con, CGRectMake(0,0,100,100));
CGContextSetFillColorWithColor(con, [UIColor blueColor].CGColor);
CGContextFillPath(con);
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// im is the blue circle image, do something with it here ...
|
你可能会对UIGraphicsBeginImageContextWithOptions这个方法的参数感到疑惑,其实第一个参数显然是将要创建的图片的大小。第二个参数表明这个图片是否是不透明的,如果我在上面的方法中传递YES而是不是NO,我的图片将会有一个黑色背景,而我不想要这种效果。第三个参数指定图片的缩放比例,传递0是告诉系统根据当前屏幕的尺寸为我自动设置压缩比例,这样我的图片就会在单分辨率和双分辨率屏幕下都能完美显示。
你不必完全使用UIKit或者Core Graphics,相反地,你可以混合UIKit 调用和Core Graphics调用来操作同样的图形上下文。它们仅仅只是表示两种不同的方式对同样的图形上下文通信而已。
CGImage绘画
UIImage在Core Graphics中的版本是CGImage(实际上是CGImageRef)。它们可以很容易地互相转换:UIImage有一个CGImage的属性,可以访问它的Quartz 的图片数据,你也可以把CGImage 转换成UIImage,使用imageWithCGImage:或者initWithCGImage:(在实战中,你会更偏向使用更加可配置性的姐妹方法:imageWithCGImage:scale:orientation: 以及 initWithCGImage:scale:orientation:)。
一个CGImage可以让你从一个原始图片的一个矩形区域中创建一个新的图片,而UIImage是做不到的。(一个CGImage还有其他强大的功能而UIImage没有的,例如你可以将图片的遮罩应用到CGImage中)。我将会通过分隔一张火星图片为两半,并分开单独绘制每一边。
注意,我们现在是在CFTypeRef范围下操作,必须自动管理好内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
UIImage* mars = [UIImage imageNamed:@ "Mars" ];
// extract each half as a CGImage
CGSize sz = mars.size;
CGImageRef marsLeft = CGImageCreateWithImageInRect([mars CGImage],
CGRectMake(0,0,sz.width/2.0,sz.height));
CGImageRef marsRight = CGImageCreateWithImageInRect([mars CGImage],
CGRectMake(sz.width/2.0,0,sz.width/2.0,sz.height));
// draw each CGImage into an image context
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(sz.width*1.5, sz.height), NO , 0);
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextDrawImage(con,
CGRectMake(0,0,sz.width/2.0,sz.height), marsLeft);
CGContextDrawImage(con,
CGRectMake(sz.width,0,sz.width/2.0,sz.height), marsRight);
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(marsLeft); CGImageRelease(marsRight)
|
但是这里的例子有个问题:绘制的东西上下颠倒了! 它不是被旋转了,而是从上到下映射,或者用专业的术语,翻转。这种想象会发生在你创建了一个CGImage,然后通过CGContextDrawImage绘制时,是由于源和目标上下文的本地坐标系统不匹配。
有多种的方式补偿这种不同坐标系统之间的不匹配。其中一种就是把CGImage绘制成一个中间的UIImage,然后从UIImage中获取CGImage,下面展示一个通用的函数来实现这种转换:
1
2
3
4
5
6
7
8
9
10
|
// Utility for flipping an image drawing
CGImageRef flip (CGImageRef im) {
CGSize sz = CGSizeMake(CGImageGetWidth(im), CGImageGetHeight(im));
UIGraphicsBeginImageContextWithOptions(sz, NO , 0);
CGContextDrawImage(UIGraphicsGetCurrentContext(),
CGRectMake(0, 0, sz.width, sz.height), im);
CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];
UIGraphicsEndImageContext();
return result;
}
|
我们可以使用这个工具函数来修复我们上面例子中调用CGContextDrawImage产生的问题,让它们正确画出火星的一半。
1
2
3
4
|
CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),
flip(marsLeft));
CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),
flip(marsRight));
|
但是,我们仍然有一个问题:在双分辨率设备上,如果我们的图片有一个双分辨率的版本(@2x.png),这个绘制就会出错。原因就是我们使用 imageNamed:来获取原始的火星图片,这样就会返回一个为了适配双分辨率而设置自己的缩放比例来产生双倍分辨率的图片。但是CGImage没有scale属性,同时对这张图片为原始分辨率两倍一无所知!因此,我们在双分辨率设备上,我们通过调用 [mars CGImage]获得到的火星CGImage图片,是火星图片大小的两倍,那么我们所有的计算都是错的。
所以,为了在CGImage提取想要的片,我们必须把所有适当的值乘以缩放比例scale,或者以CGImage的尺寸来描述大小。下面是我们在单分屏和双分屏都正确绘制的一个代码版本,并且补偿了翻转效果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
UIImage* mars = [UIImage imageNamed:@ "Mars" ];
CGSize sz = mars.size;
// Derive CGImage and use its dimensions to extract its halves
CGImageRef marsCG = [mars CGImage];
CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG),
CGImageGetHeight(marsCG));
CGImageRef marsLeft =
CGImageCreateWithImageInRect(
marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));
CGImageRef marsRight =
CGImageCreateWithImageInRect(
marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(sz.width*1.5, sz.height), NO , 0);
// The rest is as before, calling flip() to compensate for flipping
CGContextRef con = UIGraphicsGetCurrentContext();
CGContextDrawImage(con, CGRectMake(0,0,sz.width/2.0,sz.height),
flip(marsLeft));
CGContextDrawImage(con, CGRectMake(sz.width,0,sz.width/2.0,sz.height),
flip(marsRight));
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(marsLeft); CGImageRelease(marsRight);
|
另一种方案就是:在UIImage里面包装一个CGImage,绘制这个UIImage。UIImage可以通过调用 imageWithCGImage:scale:orientation:来实现这种方式,补偿缩放带来的影响。此外,通过绘制一个UIImage,而不是一个的CGImage,我们避免了翻转问题。下面是一种同时处理翻转和缩放的方法(没有调用我们上面的公用类):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
UIImage* mars = [UIImage imageNamed:@ "Mars" ];
CGSize sz = mars.size;
// Derive CGImage and use its dimensions to extract its halves
CGImageRef marsCG = [mars CGImage];
CGSize szCG = CGSizeMake(CGImageGetWidth(marsCG),
CGImageGetHeight(marsCG));
CGImageRef marsLeft =
CGImageCreateWithImageInRect(
marsCG, CGRectMake(0,0,szCG.width/2.0,szCG.height));
CGImageRef marsRight =
CGImageCreateWithImageInRect(
marsCG, CGRectMake(szCG.width/2.0,0,szCG.width/2.0,szCG.height));
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(sz.width*1.5, sz.height), NO , 0);
[[UIImage imageWithCGImage:marsLeft
scale:mars.scale
orientation:UIImageOrientationUp]
drawAtPoint:CGPointMake(0,0)];
[[UIImage imageWithCGImage:marsRight
scale:mars.scale
orientation:UIImageOrientationUp]
drawAtPoint:CGPointMake(sz.width,0)];
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(marsLeft); CGImageRelease(marsRight);
|
是的,另一种方案解决翻转,就是在绘制CGImage之前,对图形上下文进行线性转换,有效地翻转图形上下文中内部的坐标系统。这种方式很简洁,但是当有其他的线性转换时会变得难以理解。我会在下面的章节中谈论更多图形上下文转换的内容。
为什么会发生翻转??
Core Graphics 会意外发生翻转的历史,来源于OS X世界,OS X 里的坐标系统的原点默认是在左下角,正Y方向是向上的,而在iOS中,坐标原点默认在左上角,正Y方向是向下的。在大多数的绘画中没有问题,因为图形上下文的坐标系统会自动适应的。另外,在iOS的Core Graphics框架中的上下文绘画时,上下文的坐标系统原点是左上角,我们都知道,但是,创建和绘制CGImage在两个坐标系统之间,互不匹配。
Snapshots 快照
一个完整的视图 ----包括视图中的一个button、继承自这个视图的的所有视图 -----可以通过调用 drawViewHierarchyInRect:afterScreenUpdates:来绘制在当前的图形上下文中。这个方法是在iOS7中新添加的(比CGLayer提供的方法 renderInContext:快很多,已经被取代了)。得到的是原始视图的一个快照,跟原始视图看起来完全一样,只不过这个只是视图的一个位图图像,一个轻量级的虚拟拷贝。因为iOS界面的动态本质,快照功能显得非常有用。例如,你可以把一个视图的快照放在这个视图之上来隐藏发生的事情,或者在动画中展示视图移动的变化过程,而实际上只是一个快照而已。
下图展示了一个快照在我的一个应用中展示的效果。用户可以点击任意三个颜色调节按钮来调节颜色值,当颜色编辑的界面显示的时候,我想要用户有种只是临时界面的感觉,可以看到原来的界面潜伏在背后,但是原始的界面不能让用户分心,所以模糊处理了。实际上,模糊的只是原始界面的快照。
下面是图中快照的生成方式:
1
2
3
4
|
UIGraphicsBeginImageContextWithOptions(vc1.view.frame.size, YES , 0);
[vc1.view drawViewHierarchyInRect: vc1.view.frame afterScreenUpdates: NO ];
UIImage* im = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
|
接下来就是模糊化这张快照,并把它放到颜色编辑页面的下面。至于如何实现一个模糊效果就是另一个问题了。我可以会使用CIFilter(下一节的主题),但是太慢了;作为替代,我使用苹果提供的一个UIImage类别,它是作为“Blurring and Tinting an Image” 实例代码的一部分发布的。
一种更加快速获取一个视图快照的方式是使用UIView(或者UIScreen)实例方法 snapshotViewAfterScreenUpdates: 。返回的是一个UIView,而不是一个UIImage;更确切地说是一个知道怎么绘制一张图片的UIImageView,称为快照。这样的快照视图通常展示原来视图的样式,但是你也可以扩大它的边框bounds,这样快照图片也会被拉伸。如果你希望这个被拉伸的快照像可被拉伸的图片那样工作,可以用resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets方法代替。从快照视图中产生快照视图是完全合理的。
CIFilter 和CIImage
这个“CI”在CIFilter和CIImage中代表Core Image,这是一种通过数字过滤器转换图片的技术。Core Image最初在OS X系统出现。有一些OS X提供的过滤器在iOS中不可用(或许是它们对于电话设备来说,数字处理太密集了)。
其中一个过滤器就是CIFilter。可以用的过滤器(大概120种,其中有24种是iOS7新添加的)分为几类:
* 图案和渐变(Patterns ,gradients)
这些过滤器创建的CIImage可以和其它的CIImage组合在一起,例如单个颜色,棋盘图案,条纹,渐变。
* 混合(Compositing)
这些过滤器可以组合多张图片,使用合成混合模式与我们使用Photoshop图片处理程序很相似。
* 颜色(Color)
这些过滤器会适应或者修改一张图片的颜色。另外,你可以改变图片的饱和度,色度,亮度,对比度,灰度,白点,曝光度,阴影,聚焦等等。
* 几何(Geometric)
这些过滤器在图像上执行基本的几何变换,例如缩放,旋转和裁剪。
* 变换(Transformation)
这些过滤器可以扭曲,模糊或风格化图片。只有一部分适用于iOS。
* 过渡(Transition)
这些过滤器提供了一个图像与另一个之间的过渡的框架。通过调用序列中的帧,可以实现图像与图像之间的过渡动画。
* 特定目的
这些过滤器进行高度专业化的操作,如人脸检测和生成QR(二维)码。
基本使用CIFilter是相当简单的,它本质上就如过滤器字面上的意思,只是一种由键和值组成的字典罢了。你通过调用 filterWithName:方法,并提供一个过滤器的字符串形式的名称来创建过滤器;为了明白这些过滤器名称有哪些,可以查阅苹果的 Core Image Filter Reference,或者传递一个nil 值的参数调用CIFilter 类方法 filterNamesInCategories:。每一个过滤器由一小部分的键值对来决定自身的行为。你可以完全从代码中学习这些键,但是通常你还是会查阅文档。对于你感兴趣的每个键,你提供一个键 - 值对,通过调用setValue:forKey:,或通过传递键和值以及过滤器名称参数来调用filterWithName:keysAndValues:方法来实践每个效果。在传递参数时,一个数字必须封装成一个NSNumber的类型,同时系统也提供了一些支持类,如CIVector(类似CGPoint 和 CGRect的结合体),CIColor,这些都是很容易理解和掌握的。
一个CIFilter的键包括任何图像或图像上进行操作的过滤器;这样的图片必须是CIImage。你可以在过滤器的输出中获取CIImage;另外,过滤器之间可以链接在一起。但是,在过滤器链中的第一个过滤器会是什么呢?那这个过滤器操作的CIImage从哪里来呢?你可以从CGImage中,通过调用initWithCGImage:来获得一个CIImage,而你可以从UIImage的CGImage属性中获取一个CGImage。
注意:不要视图为了方便,直接从UIImage中,通过调用实例方法CIImage来获取一个CIImage。这样不会把一个UIImage转化成CIImage,它仅仅只是指向了一个已经备份了UIImage对象的CIImage,而你的图片资源并没有被CIImage备份了,而是被CGImage备份了。我下面将会解释一个被CIImage备份的UIImage是从哪里来的。
当你创建一个过滤器链时,实际上什么也没有发生。只有当你在过滤器链中转换最终的CIImage 为一个位图绘制时,密集的计算才会发生。有两种方式来实现这种效果:
* 创建一个CIContext(通过调用contextWithOptions:),然后调用createCGImage:fromRect:,把最终的CIImage作为第一个参数传递进去。这里仅有的轻微棘手的事情是,CIImage没有一个框架(frame)或边界(bounds);它有一个面积。你通常会使用这个作为createCGImage:fromRect:的第二个参数。最终输出的CGImage可用于任何地方和目的,例如显示在你的应用中、转换成一个UIImage或者用来进行下一步的绘制。
*直接通过调用UIImage类方法 imageWithCIImage:来创建一个UIImage。而获取最终的CIImage可以调用实例方法 initWithCIImage:;或者更加强大的 imageWithCIImage:scale:orientation: 又或者 initWithCIImage:scale:orientation:。你必须在这些调用之后把UIImage绘制在一些图形上下文中。最后一步是必不可少的,你需要亲自转换CIImage为一个位图,否则不会自动转换。另外,从imageWithCIImage:创建的UIImage并不能直接在UIImageView中显示,因为这个UIImage不包含任何绘制其自身的信息,它是用来绘画的,而不是用来展示的。
为了阐明上面说的内容,我会通过一张我自己的原始图片,来创建一个圆形的插图效果:如下所示
我们使用一个CIFilter来生成白色和黑色两种默认颜色之间的径向渐变效果。然后我们使用第二个CIFilter来把这个径向渐变当做我的头像和一个默认透明背景的混合遮罩,径向渐变为白色(一切渐变的内半径内)的地方,我们只是看到了我的头像,径向渐变为黑色的地方(一切渐变的外半径)则是透明的颜色,而在渐变的中间,使图像消失在圆带渐变的半径之间。从最终的过滤器链中输出的CIImage,我们可以把它转变成一个UIImage。
UIImage* moi = [UIImage imageNamed:@"Moi"]; CIImage* moi2 = [[CIImage alloc] initWithCGImage:moi.CGImage]; CGRect moiextent = moi2.extent; CIFilter* grad = [CIFilter filterWithName:@"CIRadialGradient"]; CIVector* center = [CIVector vectorWithX:moiextent.size.width/2.0 Y:moiextent.size.height/2.0]; [grad setValue:center forKey:@"inputCenter"]; [grad setValue:@85 forKey:@"inputRadius0"]; [grad setValue:@100 forKey:@"inputRadius1"]; CIImage *gradimage = [grad valueForKey: @"outputImage"]; CIFilter* blend = [CIFilter filterWithName:@"CIBlendWithMask"]; [blend setValue:moi2 forKey:@"inputImage"]; [blend setValue:gradimage forKey:@"inputMaskImage"]; CGImageRef moi3 = [[CIContext contextWithOptions:nil] createCGImage:blend.outputImage fromRect:moiextent]; UIImage* moi4 = [UIImage imageWithCGImage:moi3]; CGImageRelease(moi3);
我们可以直接捕捉CIImage作为一个UIImage,而不用由来自于链中的最后CIImage产生的CGImage来转化成一个UIImage-----但是我们在之后绘制它,以让它生成一个从过滤器链输出的位图。例如,我们可以把它绘制在图片上下文中:
UIGraphicsBeginImageContextWithOptions(moiextent.size, NO, 0); [[UIImage imageWithCIImage:blend.outputImage] drawInRect:moiextent]; UIImage* moi4 = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext();
一个过滤器链可以包含在一个简单的自定义的CIFilter子类中。你的子类需要实现 outputImage(同时还有其他方法,例如 setDefaults),下面是我子类化的CIFilter
@interface MyVignetteFilter () @property (nonatomic, strong) CIImage* inputImage; @end @implementation MyVignetteFilter -(CIImage *)outputImage { CGRect inextent = self.inputImage.extent; CIFilter* grad = [CIFilter filterWithName:@"CIRadialGradient"]; CIVector* center = [CIVector vectorWithX:inextent.size.width/2.0 Y:inextent.size.height/2.0]; [grad setValue:center forKey:@"inputCenter"]; [grad setValue:@85 forKey:@"inputRadius0"]; [grad setValue:@100 forKey:@"inputRadius1"]; CIImage *gradimage = [grad valueForKey: @"outputImage"]; CIFilter* blend = [CIFilter filterWithName:@"CIBlendWithMask"]; [blend setValue:self.inputImage forKey:@"inputImage"]; [blend setValue:gradimage forKey:@"inputMaskImage"]; return blend.outputImage; } @end
而下面的这是如何使用这个CIFilter的子类,同时展示它的输出:
CIFilter* vig = [MyVignetteFilter new]; CIImage* im = [CIImage imageWithCGImage:[UIImage imageNamed:@"Moi"].CGImage]; [vig setValue:im forKey:@"inputImage"]; CIImage* outim = vig.outputImage; UIGraphicsBeginImageContextWithOptions(outim.extent.size, NO, 0); [[UIImage imageWithCIImage:outim] drawInRect:outim.extent]; UIImage* result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); self.iv.image = result;